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