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.

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