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.

495 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2017 - ROLI Ltd.
  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, vs2019, 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 : 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 : 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. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  185. "Connection error",
  186. messageText,
  187. "OK");
  188. }
  189. //==============================================================================
  190. Slider rotaryKnob;
  191. OSCSender sender1, sender2;
  192. Label senderLabel { {}, "Sender" };
  193. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCSenderDemo)
  194. };
  195. //==============================================================================
  196. class OSCReceiverDemo : public Component,
  197. private OSCReceiver,
  198. private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>
  199. {
  200. public:
  201. //==============================================================================
  202. OSCReceiverDemo()
  203. {
  204. addAndMakeVisible (receiverLabel);
  205. receiverLabel.attachToComponent (&rotaryKnob, false);
  206. rotaryKnob.setRange (0.0, 1.0);
  207. rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
  208. rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
  209. rotaryKnob.setBounds (50, 50, 180, 180);
  210. rotaryKnob.setInterceptsMouseClicks (false, false);
  211. addAndMakeVisible (rotaryKnob);
  212. // specify here on which UDP port number to receive incoming OSC messages
  213. if (! connect (9001))
  214. showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
  215. // tell the component to listen for OSC messages matching this address:
  216. addListener (this, "/juce/rotaryknob");
  217. }
  218. private:
  219. //==============================================================================
  220. void oscMessageReceived (const OSCMessage& message) override
  221. {
  222. if (message.size() == 1 && message[0].isFloat32())
  223. rotaryKnob.setValue (jlimit (0.0f, 10.0f, message[0].getFloat32()));
  224. }
  225. void showConnectionErrorMessage (const String& messageText)
  226. {
  227. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  228. "Connection error",
  229. messageText,
  230. "OK");
  231. }
  232. //==============================================================================
  233. Slider rotaryKnob;
  234. Label receiverLabel { {}, "Receiver" };
  235. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCReceiverDemo)
  236. };
  237. //==============================================================================
  238. class OSCMonitorDemo : public Component,
  239. private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
  240. {
  241. public:
  242. //==============================================================================
  243. OSCMonitorDemo()
  244. {
  245. portNumberLabel.setBounds (10, 18, 130, 25);
  246. addAndMakeVisible (portNumberLabel);
  247. portNumberField.setEditable (true, true, true);
  248. portNumberField.setBounds (140, 18, 50, 25);
  249. addAndMakeVisible (portNumberField);
  250. connectButton.setBounds (210, 18, 100, 25);
  251. addAndMakeVisible (connectButton);
  252. connectButton.onClick = [this] { connectButtonClicked(); };
  253. clearButton.setBounds (320, 18, 60, 25);
  254. addAndMakeVisible (clearButton);
  255. clearButton.onClick = [this] { clearButtonClicked(); };
  256. connectionStatusLabel.setBounds (450, 18, 240, 25);
  257. updateConnectionStatusLabel();
  258. addAndMakeVisible (connectionStatusLabel);
  259. oscLogListBox.setBounds (0, 60, 700, 340);
  260. addAndMakeVisible (oscLogListBox);
  261. oscReceiver.addListener (this);
  262. oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize)
  263. {
  264. oscLogListBox.addInvalidOSCPacket (data, dataSize);
  265. });
  266. }
  267. private:
  268. //==============================================================================
  269. Label portNumberLabel { {}, "UDP Port Number: " };
  270. Label portNumberField { {}, "9002" };
  271. TextButton connectButton { "Connect" };
  272. TextButton clearButton { "Clear" };
  273. Label connectionStatusLabel;
  274. OSCLogListBox oscLogListBox;
  275. OSCReceiver oscReceiver;
  276. int currentPortNumber = -1;
  277. //==============================================================================
  278. void connectButtonClicked()
  279. {
  280. if (! isConnected())
  281. connect();
  282. else
  283. disconnect();
  284. updateConnectionStatusLabel();
  285. }
  286. //==============================================================================
  287. void clearButtonClicked()
  288. {
  289. oscLogListBox.clear();
  290. }
  291. //==============================================================================
  292. void oscMessageReceived (const OSCMessage& message) override
  293. {
  294. oscLogListBox.addOSCMessage (message);
  295. }
  296. void oscBundleReceived (const OSCBundle& bundle) override
  297. {
  298. oscLogListBox.addOSCBundle (bundle);
  299. }
  300. //==============================================================================
  301. void connect()
  302. {
  303. auto portToConnect = portNumberField.getText().getIntValue();
  304. if (! isValidOscPort (portToConnect))
  305. {
  306. handleInvalidPortNumberEntered();
  307. return;
  308. }
  309. if (oscReceiver.connect (portToConnect))
  310. {
  311. currentPortNumber = portToConnect;
  312. connectButton.setButtonText ("Disconnect");
  313. }
  314. else
  315. {
  316. handleConnectError (portToConnect);
  317. }
  318. }
  319. //==============================================================================
  320. void disconnect()
  321. {
  322. if (oscReceiver.disconnect())
  323. {
  324. currentPortNumber = -1;
  325. connectButton.setButtonText ("Connect");
  326. }
  327. else
  328. {
  329. handleDisconnectError();
  330. }
  331. }
  332. //==============================================================================
  333. void handleConnectError (int failedPort)
  334. {
  335. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  336. "OSC Connection error",
  337. "Error: could not connect to port " + String (failedPort),
  338. "OK");
  339. }
  340. //==============================================================================
  341. void handleDisconnectError()
  342. {
  343. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  344. "Unknown error",
  345. "An unknown error occurred while trying to disconnect from UDP port.",
  346. "OK");
  347. }
  348. //==============================================================================
  349. void handleInvalidPortNumberEntered()
  350. {
  351. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  352. "Invalid port number",
  353. "Error: you have entered an invalid UDP port number.",
  354. "OK");
  355. }
  356. //==============================================================================
  357. bool isConnected() const
  358. {
  359. return currentPortNumber != -1;
  360. }
  361. //==============================================================================
  362. bool isValidOscPort (int port) const
  363. {
  364. return port > 0 && port < 65536;
  365. }
  366. //==============================================================================
  367. void updateConnectionStatusLabel()
  368. {
  369. String text = "Status: ";
  370. if (isConnected())
  371. text += "Connected to UDP port " + String (currentPortNumber);
  372. else
  373. text += "Disconnected";
  374. auto textColour = isConnected() ? Colours::green : Colours::red;
  375. connectionStatusLabel.setText (text, dontSendNotification);
  376. connectionStatusLabel.setFont (Font (15.00f, Font::bold));
  377. connectionStatusLabel.setColour (Label::textColourId, textColour);
  378. connectionStatusLabel.setJustificationType (Justification::centredRight);
  379. }
  380. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCMonitorDemo)
  381. };
  382. //==============================================================================
  383. class OSCDemo : public Component
  384. {
  385. public:
  386. OSCDemo()
  387. {
  388. addAndMakeVisible (monitor);
  389. addAndMakeVisible (receiver);
  390. addAndMakeVisible (sender);
  391. setSize (700, 400);
  392. }
  393. void resized() override
  394. {
  395. auto bounds = getLocalBounds();
  396. auto lowerBounds = bounds.removeFromBottom (getHeight() / 2);
  397. auto halfBounds = bounds.removeFromRight (getWidth() / 2);
  398. sender .setBounds (bounds);
  399. receiver.setBounds (halfBounds);
  400. monitor .setBounds (lowerBounds.removeFromTop (getHeight() / 2));
  401. }
  402. private:
  403. OSCMonitorDemo monitor;
  404. OSCReceiverDemo receiver;
  405. OSCSenderDemo sender;
  406. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCDemo)
  407. };