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.

499 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: OSCDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Application using the OSC protocol.
  24. dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
  25. juce_gui_basics, juce_osc
  26. exporters: xcode_mac, vs2022, linux_make
  27. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  28. type: Component
  29. mainClass: OSCDemo
  30. useLocalCopy: 1
  31. END_JUCE_PIP_METADATA
  32. *******************************************************************************/
  33. #pragma once
  34. //==============================================================================
  35. class OSCLogListBox final : public ListBox,
  36. private ListBoxModel,
  37. private AsyncUpdater
  38. {
  39. public:
  40. OSCLogListBox()
  41. {
  42. setModel (this);
  43. }
  44. ~OSCLogListBox() override = default;
  45. //==============================================================================
  46. int getNumRows() override
  47. {
  48. return oscLogList.size();
  49. }
  50. //==============================================================================
  51. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  52. {
  53. ignoreUnused (rowIsSelected);
  54. if (isPositiveAndBelow (row, oscLogList.size()))
  55. {
  56. g.setColour (Colours::white);
  57. g.drawText (oscLogList[row],
  58. Rectangle<int> (width, height).reduced (4, 0),
  59. Justification::centredLeft, true);
  60. }
  61. }
  62. //==============================================================================
  63. void addOSCMessage (const OSCMessage& message, int level = 0)
  64. {
  65. oscLogList.add (getIndentationString (level)
  66. + "- osc message, address = '"
  67. + message.getAddressPattern().toString()
  68. + "', "
  69. + String (message.size())
  70. + " argument(s)");
  71. if (! message.isEmpty())
  72. {
  73. for (auto& arg : message)
  74. addOSCMessageArgument (arg, level + 1);
  75. }
  76. triggerAsyncUpdate();
  77. }
  78. //==============================================================================
  79. void addOSCBundle (const OSCBundle& bundle, int level = 0)
  80. {
  81. OSCTimeTag timeTag = bundle.getTimeTag();
  82. oscLogList.add (getIndentationString (level)
  83. + "- osc bundle, time tag = "
  84. + timeTag.toTime().toString (true, true, true, true));
  85. for (auto& element : bundle)
  86. {
  87. if (element.isMessage())
  88. addOSCMessage (element.getMessage(), level + 1);
  89. else if (element.isBundle())
  90. addOSCBundle (element.getBundle(), level + 1);
  91. }
  92. triggerAsyncUpdate();
  93. }
  94. //==============================================================================
  95. void addOSCMessageArgument (const OSCArgument& arg, int level)
  96. {
  97. String typeAsString;
  98. String valueAsString;
  99. if (arg.isFloat32())
  100. {
  101. typeAsString = "float32";
  102. valueAsString = String (arg.getFloat32());
  103. }
  104. else if (arg.isInt32())
  105. {
  106. typeAsString = "int32";
  107. valueAsString = String (arg.getInt32());
  108. }
  109. else if (arg.isString())
  110. {
  111. typeAsString = "string";
  112. valueAsString = arg.getString();
  113. }
  114. else if (arg.isBlob())
  115. {
  116. typeAsString = "blob";
  117. auto& blob = arg.getBlob();
  118. valueAsString = String::fromUTF8 ((const char*) blob.getData(), (int) blob.getSize());
  119. }
  120. else
  121. {
  122. typeAsString = "(unknown)";
  123. }
  124. oscLogList.add (getIndentationString (level + 1) + "- " + typeAsString.paddedRight (' ', 12) + valueAsString);
  125. }
  126. //==============================================================================
  127. void addInvalidOSCPacket (const char* /* data */, int dataSize)
  128. {
  129. oscLogList.add ("- (" + String (dataSize) + "bytes with invalid format)");
  130. }
  131. //==============================================================================
  132. void clear()
  133. {
  134. oscLogList.clear();
  135. triggerAsyncUpdate();
  136. }
  137. //==============================================================================
  138. void handleAsyncUpdate() override
  139. {
  140. updateContent();
  141. scrollToEnsureRowIsOnscreen (oscLogList.size() - 1);
  142. repaint();
  143. }
  144. private:
  145. static String getIndentationString (int level)
  146. {
  147. return String().paddedRight (' ', 2 * level);
  148. }
  149. //==============================================================================
  150. StringArray oscLogList;
  151. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCLogListBox)
  152. };
  153. //==============================================================================
  154. class OSCSenderDemo final : public Component
  155. {
  156. public:
  157. OSCSenderDemo()
  158. {
  159. addAndMakeVisible (senderLabel);
  160. senderLabel.attachToComponent (&rotaryKnob, false);
  161. rotaryKnob.setRange (0.0, 1.0);
  162. rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
  163. rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
  164. rotaryKnob.setBounds (50, 50, 180, 180);
  165. addAndMakeVisible (rotaryKnob);
  166. rotaryKnob.onValueChange = [this]
  167. {
  168. // create and send an OSC message with an address and a float value:
  169. if (! sender1.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
  170. showConnectionErrorMessage ("Error: could not send OSC message.");
  171. if (! sender2.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
  172. showConnectionErrorMessage ("Error: could not send OSC message.");
  173. };
  174. // specify here where to send OSC messages to: host URL and UDP port number
  175. if (! sender1.connect ("127.0.0.1", 9001))
  176. showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
  177. if (! sender2.connect ("127.0.0.1", 9002))
  178. showConnectionErrorMessage ("Error: could not connect to UDP port 9002.");
  179. }
  180. private:
  181. //==============================================================================
  182. void showConnectionErrorMessage (const String& messageText)
  183. {
  184. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  185. "Connection error",
  186. messageText);
  187. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  188. }
  189. //==============================================================================
  190. Slider rotaryKnob;
  191. OSCSender sender1, sender2;
  192. Label senderLabel { {}, "Sender" };
  193. ScopedMessageBox messageBox;
  194. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCSenderDemo)
  195. };
  196. //==============================================================================
  197. class OSCReceiverDemo final : public Component,
  198. private OSCReceiver,
  199. private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>
  200. {
  201. public:
  202. //==============================================================================
  203. OSCReceiverDemo()
  204. {
  205. addAndMakeVisible (receiverLabel);
  206. receiverLabel.attachToComponent (&rotaryKnob, false);
  207. rotaryKnob.setRange (0.0, 1.0);
  208. rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
  209. rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
  210. rotaryKnob.setBounds (50, 50, 180, 180);
  211. rotaryKnob.setInterceptsMouseClicks (false, false);
  212. addAndMakeVisible (rotaryKnob);
  213. // specify here on which UDP port number to receive incoming OSC messages
  214. if (! connect (9001))
  215. showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
  216. // tell the component to listen for OSC messages matching this address:
  217. addListener (this, "/juce/rotaryknob");
  218. }
  219. private:
  220. //==============================================================================
  221. void oscMessageReceived (const OSCMessage& message) override
  222. {
  223. if (message.size() == 1 && message[0].isFloat32())
  224. rotaryKnob.setValue (jlimit (0.0f, 10.0f, message[0].getFloat32()));
  225. }
  226. void showConnectionErrorMessage (const String& messageText)
  227. {
  228. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  229. "Connection error",
  230. messageText);
  231. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  232. }
  233. //==============================================================================
  234. Slider rotaryKnob;
  235. Label receiverLabel { {}, "Receiver" };
  236. ScopedMessageBox messageBox;
  237. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCReceiverDemo)
  238. };
  239. //==============================================================================
  240. class OSCMonitorDemo final : public Component,
  241. private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
  242. {
  243. public:
  244. //==============================================================================
  245. OSCMonitorDemo()
  246. {
  247. portNumberLabel.setBounds (10, 18, 130, 25);
  248. addAndMakeVisible (portNumberLabel);
  249. portNumberField.setEditable (true, true, true);
  250. portNumberField.setBounds (140, 18, 50, 25);
  251. addAndMakeVisible (portNumberField);
  252. connectButton.setBounds (210, 18, 100, 25);
  253. addAndMakeVisible (connectButton);
  254. connectButton.onClick = [this] { connectButtonClicked(); };
  255. clearButton.setBounds (320, 18, 60, 25);
  256. addAndMakeVisible (clearButton);
  257. clearButton.onClick = [this] { clearButtonClicked(); };
  258. connectionStatusLabel.setBounds (450, 18, 240, 25);
  259. updateConnectionStatusLabel();
  260. addAndMakeVisible (connectionStatusLabel);
  261. oscLogListBox.setBounds (0, 60, 700, 340);
  262. addAndMakeVisible (oscLogListBox);
  263. oscReceiver.addListener (this);
  264. oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize)
  265. {
  266. oscLogListBox.addInvalidOSCPacket (data, dataSize);
  267. });
  268. }
  269. private:
  270. //==============================================================================
  271. Label portNumberLabel { {}, "UDP Port Number: " };
  272. Label portNumberField { {}, "9002" };
  273. TextButton connectButton { "Connect" };
  274. TextButton clearButton { "Clear" };
  275. Label connectionStatusLabel;
  276. OSCLogListBox oscLogListBox;
  277. OSCReceiver oscReceiver;
  278. int currentPortNumber = -1;
  279. //==============================================================================
  280. void connectButtonClicked()
  281. {
  282. if (! isConnected())
  283. connect();
  284. else
  285. disconnect();
  286. updateConnectionStatusLabel();
  287. }
  288. //==============================================================================
  289. void clearButtonClicked()
  290. {
  291. oscLogListBox.clear();
  292. }
  293. //==============================================================================
  294. void oscMessageReceived (const OSCMessage& message) override
  295. {
  296. oscLogListBox.addOSCMessage (message);
  297. }
  298. void oscBundleReceived (const OSCBundle& bundle) override
  299. {
  300. oscLogListBox.addOSCBundle (bundle);
  301. }
  302. //==============================================================================
  303. void connect()
  304. {
  305. auto portToConnect = portNumberField.getText().getIntValue();
  306. if (! isValidOscPort (portToConnect))
  307. {
  308. handleInvalidPortNumberEntered();
  309. return;
  310. }
  311. if (oscReceiver.connect (portToConnect))
  312. {
  313. currentPortNumber = portToConnect;
  314. connectButton.setButtonText ("Disconnect");
  315. }
  316. else
  317. {
  318. handleConnectError (portToConnect);
  319. }
  320. }
  321. //==============================================================================
  322. void disconnect()
  323. {
  324. if (oscReceiver.disconnect())
  325. {
  326. currentPortNumber = -1;
  327. connectButton.setButtonText ("Connect");
  328. }
  329. else
  330. {
  331. handleDisconnectError();
  332. }
  333. }
  334. //==============================================================================
  335. void handleConnectError (int failedPort)
  336. {
  337. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  338. "OSC Connection error",
  339. "Error: could not connect to port " + String (failedPort));
  340. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  341. }
  342. //==============================================================================
  343. void handleDisconnectError()
  344. {
  345. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  346. "Unknown error",
  347. "An unknown error occurred while trying to disconnect from UDP port.");
  348. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  349. }
  350. //==============================================================================
  351. void handleInvalidPortNumberEntered()
  352. {
  353. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  354. "Invalid port number",
  355. "Error: you have entered an invalid UDP port number.");
  356. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  357. }
  358. //==============================================================================
  359. bool isConnected() const
  360. {
  361. return currentPortNumber != -1;
  362. }
  363. //==============================================================================
  364. bool isValidOscPort (int port) const
  365. {
  366. return port > 0 && port < 65536;
  367. }
  368. //==============================================================================
  369. void updateConnectionStatusLabel()
  370. {
  371. String text = "Status: ";
  372. if (isConnected())
  373. text += "Connected to UDP port " + String (currentPortNumber);
  374. else
  375. text += "Disconnected";
  376. auto textColour = isConnected() ? Colours::green : Colours::red;
  377. connectionStatusLabel.setText (text, dontSendNotification);
  378. connectionStatusLabel.setFont (Font (15.00f, Font::bold));
  379. connectionStatusLabel.setColour (Label::textColourId, textColour);
  380. connectionStatusLabel.setJustificationType (Justification::centredRight);
  381. }
  382. ScopedMessageBox messageBox;
  383. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCMonitorDemo)
  384. };
  385. //==============================================================================
  386. class OSCDemo final : public Component
  387. {
  388. public:
  389. OSCDemo()
  390. {
  391. addAndMakeVisible (monitor);
  392. addAndMakeVisible (receiver);
  393. addAndMakeVisible (sender);
  394. setSize (700, 400);
  395. }
  396. void resized() override
  397. {
  398. auto bounds = getLocalBounds();
  399. auto lowerBounds = bounds.removeFromBottom (getHeight() / 2);
  400. auto halfBounds = bounds.removeFromRight (getWidth() / 2);
  401. sender .setBounds (bounds);
  402. receiver.setBounds (halfBounds);
  403. monitor .setBounds (lowerBounds.removeFromTop (getHeight() / 2));
  404. }
  405. private:
  406. OSCMonitorDemo monitor;
  407. OSCReceiverDemo receiver;
  408. OSCSenderDemo sender;
  409. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCDemo)
  410. };