495 lines
17 KiB
C
495 lines
17 KiB
C
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE examples.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
The code included in this file is provided under the terms of the ISC license
|
||
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
||
|
without fee is hereby granted provided that the above copyright notice and
|
||
|
this permission notice appear in all copies.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||
|
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||
|
PURPOSE, ARE DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
/*******************************************************************************
|
||
|
The block below describes the properties of this PIP. A PIP is a short snippet
|
||
|
of code that can be read by the Projucer and used to generate a JUCE project.
|
||
|
|
||
|
BEGIN_JUCE_PIP_METADATA
|
||
|
|
||
|
name: OSCDemo
|
||
|
version: 1.0.0
|
||
|
vendor: JUCE
|
||
|
website: http://juce.com
|
||
|
description: Application using the OSC protocol.
|
||
|
|
||
|
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||
|
juce_gui_basics, juce_osc
|
||
|
exporters: xcode_mac, vs2019, linux_make
|
||
|
|
||
|
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||
|
|
||
|
type: Component
|
||
|
mainClass: OSCDemo
|
||
|
|
||
|
useLocalCopy: 1
|
||
|
|
||
|
END_JUCE_PIP_METADATA
|
||
|
|
||
|
*******************************************************************************/
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
class OSCLogListBox : public ListBox,
|
||
|
private ListBoxModel,
|
||
|
private AsyncUpdater
|
||
|
{
|
||
|
public:
|
||
|
OSCLogListBox()
|
||
|
{
|
||
|
setModel (this);
|
||
|
}
|
||
|
|
||
|
~OSCLogListBox() override = default;
|
||
|
|
||
|
//==============================================================================
|
||
|
int getNumRows() override
|
||
|
{
|
||
|
return oscLogList.size();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
|
||
|
{
|
||
|
ignoreUnused (rowIsSelected);
|
||
|
|
||
|
if (isPositiveAndBelow (row, oscLogList.size()))
|
||
|
{
|
||
|
g.setColour (Colours::white);
|
||
|
|
||
|
g.drawText (oscLogList[row],
|
||
|
Rectangle<int> (width, height).reduced (4, 0),
|
||
|
Justification::centredLeft, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void addOSCMessage (const OSCMessage& message, int level = 0)
|
||
|
{
|
||
|
oscLogList.add (getIndentationString (level)
|
||
|
+ "- osc message, address = '"
|
||
|
+ message.getAddressPattern().toString()
|
||
|
+ "', "
|
||
|
+ String (message.size())
|
||
|
+ " argument(s)");
|
||
|
|
||
|
if (! message.isEmpty())
|
||
|
{
|
||
|
for (auto& arg : message)
|
||
|
addOSCMessageArgument (arg, level + 1);
|
||
|
}
|
||
|
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void addOSCBundle (const OSCBundle& bundle, int level = 0)
|
||
|
{
|
||
|
OSCTimeTag timeTag = bundle.getTimeTag();
|
||
|
|
||
|
oscLogList.add (getIndentationString (level)
|
||
|
+ "- osc bundle, time tag = "
|
||
|
+ timeTag.toTime().toString (true, true, true, true));
|
||
|
|
||
|
for (auto& element : bundle)
|
||
|
{
|
||
|
if (element.isMessage())
|
||
|
addOSCMessage (element.getMessage(), level + 1);
|
||
|
else if (element.isBundle())
|
||
|
addOSCBundle (element.getBundle(), level + 1);
|
||
|
}
|
||
|
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void addOSCMessageArgument (const OSCArgument& arg, int level)
|
||
|
{
|
||
|
String typeAsString;
|
||
|
String valueAsString;
|
||
|
|
||
|
if (arg.isFloat32())
|
||
|
{
|
||
|
typeAsString = "float32";
|
||
|
valueAsString = String (arg.getFloat32());
|
||
|
}
|
||
|
else if (arg.isInt32())
|
||
|
{
|
||
|
typeAsString = "int32";
|
||
|
valueAsString = String (arg.getInt32());
|
||
|
}
|
||
|
else if (arg.isString())
|
||
|
{
|
||
|
typeAsString = "string";
|
||
|
valueAsString = arg.getString();
|
||
|
}
|
||
|
else if (arg.isBlob())
|
||
|
{
|
||
|
typeAsString = "blob";
|
||
|
auto& blob = arg.getBlob();
|
||
|
valueAsString = String::fromUTF8 ((const char*) blob.getData(), (int) blob.getSize());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
typeAsString = "(unknown)";
|
||
|
}
|
||
|
|
||
|
oscLogList.add (getIndentationString (level + 1) + "- " + typeAsString.paddedRight(' ', 12) + valueAsString);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void addInvalidOSCPacket (const char* /* data */, int dataSize)
|
||
|
{
|
||
|
oscLogList.add ("- (" + String(dataSize) + "bytes with invalid format)");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void clear()
|
||
|
{
|
||
|
oscLogList.clear();
|
||
|
triggerAsyncUpdate();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void handleAsyncUpdate() override
|
||
|
{
|
||
|
updateContent();
|
||
|
scrollToEnsureRowIsOnscreen (oscLogList.size() - 1);
|
||
|
repaint();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static String getIndentationString (int level)
|
||
|
{
|
||
|
return String().paddedRight (' ', 2 * level);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
StringArray oscLogList;
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCLogListBox)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
class OSCSenderDemo : public Component
|
||
|
{
|
||
|
public:
|
||
|
OSCSenderDemo()
|
||
|
{
|
||
|
addAndMakeVisible (senderLabel);
|
||
|
senderLabel.attachToComponent (&rotaryKnob, false);
|
||
|
|
||
|
rotaryKnob.setRange (0.0, 1.0);
|
||
|
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
|
||
|
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
|
||
|
rotaryKnob.setBounds (50, 50, 180, 180);
|
||
|
addAndMakeVisible (rotaryKnob);
|
||
|
rotaryKnob.onValueChange = [this]
|
||
|
{
|
||
|
// create and send an OSC message with an address and a float value:
|
||
|
if (! sender1.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
|
||
|
showConnectionErrorMessage ("Error: could not send OSC message.");
|
||
|
if (! sender2.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
|
||
|
showConnectionErrorMessage ("Error: could not send OSC message.");
|
||
|
};
|
||
|
|
||
|
// specify here where to send OSC messages to: host URL and UDP port number
|
||
|
if (! sender1.connect ("127.0.0.1", 9001))
|
||
|
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
|
||
|
if (! sender2.connect ("127.0.0.1", 9002))
|
||
|
showConnectionErrorMessage ("Error: could not connect to UDP port 9002.");
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
void showConnectionErrorMessage (const String& messageText)
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"Connection error",
|
||
|
messageText,
|
||
|
"OK");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
Slider rotaryKnob;
|
||
|
OSCSender sender1, sender2;
|
||
|
Label senderLabel { {}, "Sender" };
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCSenderDemo)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
class OSCReceiverDemo : public Component,
|
||
|
private OSCReceiver,
|
||
|
private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>
|
||
|
{
|
||
|
public:
|
||
|
//==============================================================================
|
||
|
OSCReceiverDemo()
|
||
|
{
|
||
|
addAndMakeVisible (receiverLabel);
|
||
|
receiverLabel.attachToComponent (&rotaryKnob, false);
|
||
|
|
||
|
rotaryKnob.setRange (0.0, 1.0);
|
||
|
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
|
||
|
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
|
||
|
rotaryKnob.setBounds (50, 50, 180, 180);
|
||
|
rotaryKnob.setInterceptsMouseClicks (false, false);
|
||
|
addAndMakeVisible (rotaryKnob);
|
||
|
|
||
|
// specify here on which UDP port number to receive incoming OSC messages
|
||
|
if (! connect (9001))
|
||
|
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
|
||
|
|
||
|
// tell the component to listen for OSC messages matching this address:
|
||
|
addListener (this, "/juce/rotaryknob");
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
void oscMessageReceived (const OSCMessage& message) override
|
||
|
{
|
||
|
if (message.size() == 1 && message[0].isFloat32())
|
||
|
rotaryKnob.setValue (jlimit (0.0f, 10.0f, message[0].getFloat32()));
|
||
|
}
|
||
|
|
||
|
void showConnectionErrorMessage (const String& messageText)
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"Connection error",
|
||
|
messageText,
|
||
|
"OK");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
Slider rotaryKnob;
|
||
|
Label receiverLabel { {}, "Receiver" };
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCReceiverDemo)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
class OSCMonitorDemo : public Component,
|
||
|
private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
|
||
|
{
|
||
|
public:
|
||
|
//==============================================================================
|
||
|
OSCMonitorDemo()
|
||
|
{
|
||
|
portNumberLabel.setBounds (10, 18, 130, 25);
|
||
|
addAndMakeVisible (portNumberLabel);
|
||
|
|
||
|
portNumberField.setEditable (true, true, true);
|
||
|
portNumberField.setBounds (140, 18, 50, 25);
|
||
|
addAndMakeVisible (portNumberField);
|
||
|
|
||
|
connectButton.setBounds (210, 18, 100, 25);
|
||
|
addAndMakeVisible (connectButton);
|
||
|
connectButton.onClick = [this] { connectButtonClicked(); };
|
||
|
|
||
|
clearButton.setBounds (320, 18, 60, 25);
|
||
|
addAndMakeVisible (clearButton);
|
||
|
clearButton.onClick = [this] { clearButtonClicked(); };
|
||
|
|
||
|
connectionStatusLabel.setBounds (450, 18, 240, 25);
|
||
|
updateConnectionStatusLabel();
|
||
|
addAndMakeVisible (connectionStatusLabel);
|
||
|
|
||
|
oscLogListBox.setBounds (0, 60, 700, 340);
|
||
|
addAndMakeVisible (oscLogListBox);
|
||
|
|
||
|
oscReceiver.addListener (this);
|
||
|
oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize)
|
||
|
{
|
||
|
oscLogListBox.addInvalidOSCPacket (data, dataSize);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
Label portNumberLabel { {}, "UDP Port Number: " };
|
||
|
Label portNumberField { {}, "9002" };
|
||
|
TextButton connectButton { "Connect" };
|
||
|
TextButton clearButton { "Clear" };
|
||
|
Label connectionStatusLabel;
|
||
|
|
||
|
OSCLogListBox oscLogListBox;
|
||
|
OSCReceiver oscReceiver;
|
||
|
|
||
|
int currentPortNumber = -1;
|
||
|
|
||
|
//==============================================================================
|
||
|
void connectButtonClicked()
|
||
|
{
|
||
|
if (! isConnected())
|
||
|
connect();
|
||
|
else
|
||
|
disconnect();
|
||
|
|
||
|
updateConnectionStatusLabel();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void clearButtonClicked()
|
||
|
{
|
||
|
oscLogListBox.clear();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void oscMessageReceived (const OSCMessage& message) override
|
||
|
{
|
||
|
oscLogListBox.addOSCMessage (message);
|
||
|
}
|
||
|
|
||
|
void oscBundleReceived (const OSCBundle& bundle) override
|
||
|
{
|
||
|
oscLogListBox.addOSCBundle (bundle);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void connect()
|
||
|
{
|
||
|
auto portToConnect = portNumberField.getText().getIntValue();
|
||
|
|
||
|
if (! isValidOscPort (portToConnect))
|
||
|
{
|
||
|
handleInvalidPortNumberEntered();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (oscReceiver.connect (portToConnect))
|
||
|
{
|
||
|
currentPortNumber = portToConnect;
|
||
|
connectButton.setButtonText ("Disconnect");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
handleConnectError (portToConnect);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void disconnect()
|
||
|
{
|
||
|
if (oscReceiver.disconnect())
|
||
|
{
|
||
|
currentPortNumber = -1;
|
||
|
connectButton.setButtonText ("Connect");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
handleDisconnectError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void handleConnectError (int failedPort)
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"OSC Connection error",
|
||
|
"Error: could not connect to port " + String (failedPort),
|
||
|
"OK");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void handleDisconnectError()
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"Unknown error",
|
||
|
"An unknown error occurred while trying to disconnect from UDP port.",
|
||
|
"OK");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void handleInvalidPortNumberEntered()
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"Invalid port number",
|
||
|
"Error: you have entered an invalid UDP port number.",
|
||
|
"OK");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool isConnected() const
|
||
|
{
|
||
|
return currentPortNumber != -1;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool isValidOscPort (int port) const
|
||
|
{
|
||
|
return port > 0 && port < 65536;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void updateConnectionStatusLabel()
|
||
|
{
|
||
|
String text = "Status: ";
|
||
|
|
||
|
if (isConnected())
|
||
|
text += "Connected to UDP port " + String (currentPortNumber);
|
||
|
else
|
||
|
text += "Disconnected";
|
||
|
|
||
|
auto textColour = isConnected() ? Colours::green : Colours::red;
|
||
|
|
||
|
connectionStatusLabel.setText (text, dontSendNotification);
|
||
|
connectionStatusLabel.setFont (Font (15.00f, Font::bold));
|
||
|
connectionStatusLabel.setColour (Label::textColourId, textColour);
|
||
|
connectionStatusLabel.setJustificationType (Justification::centredRight);
|
||
|
}
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCMonitorDemo)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
class OSCDemo : public Component
|
||
|
{
|
||
|
public:
|
||
|
OSCDemo()
|
||
|
{
|
||
|
addAndMakeVisible (monitor);
|
||
|
addAndMakeVisible (receiver);
|
||
|
addAndMakeVisible (sender);
|
||
|
|
||
|
setSize (700, 400);
|
||
|
}
|
||
|
|
||
|
void resized() override
|
||
|
{
|
||
|
auto bounds = getLocalBounds();
|
||
|
|
||
|
auto lowerBounds = bounds.removeFromBottom (getHeight() / 2);
|
||
|
auto halfBounds = bounds.removeFromRight (getWidth() / 2);
|
||
|
|
||
|
sender .setBounds (bounds);
|
||
|
receiver.setBounds (halfBounds);
|
||
|
monitor .setBounds (lowerBounds.removeFromTop (getHeight() / 2));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
OSCMonitorDemo monitor;
|
||
|
OSCReceiverDemo receiver;
|
||
|
OSCSenderDemo sender;
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCDemo)
|
||
|
};
|