Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
236e3fa
Add OSCManager component
mendeni Jan 16, 2026
f342f3a
Merge remote-tracking branch 'origin/1.7.5' into add-juce-osc
mendeni Jan 20, 2026
1a0ac43
Add generic control registration system for OSC receive on selected c…
Copilot Jan 23, 2026
c1bba80
Extend OSC functionality with configurable targets, outbound messages…
Copilot Jan 24, 2026
da711ca
Merge remote-tracking branch 'origin/1.7.5' into add-juce-osc
mendeni Jan 24, 2026
ecafd17
Add OSC support for remaining UI controls (main, options, recording t…
Copilot Jan 25, 2026
99acf9a
Add OSC controls for remote peer management and FX with state broadca…
Copilot Jan 27, 2026
ecd67f1
Update OSC BufferMinButton, RecvSyncButton to accept Float32 instead …
mendeni Jan 27, 2026
c9a91d9
OSC: Deduplicate peer clearing logic and fix Disconnect button (#21)
Copilot Jan 28, 2026
21e1e42
Only build Standalone and VST3 for macOS and Linux
mendeni Jan 28, 2026
d42da4b
Add bidirectional OSC scaling for TouchOSC slider synchronization (#22)
Copilot Jan 28, 2026
9a63463
Change Peer Mute/Solo OSC controls from int32 to float32 (#23)
Copilot Jan 29, 2026
dcaabba
Add recording options to OSC state synchronization (#24)
Copilot Jan 29, 2026
fdbfd65
Fix OSC peer leave: call listeners before peer removal (#25)
Copilot Jan 29, 2026
30f5b7b
Add TouchOSC with Control, Session, Recording pages
mendeni Jan 29, 2026
34b9fb5
Verbose copy of VST3 for easier debugging
mendeni Jan 29, 2026
35b3976
Add SonoBusMendeni.tosc to build artefacts
mendeni Jan 29, 2026
527c8f7
Fix cp SonoBusMendeni.tosc in build artefacts
mendeni Jan 29, 2026
bb6e3e8
Include SonoBusMendeni.tosc in build artefact upload
mendeni Jan 29, 2026
b7365f8
Include SonoBusMendeni.tosc in macOS artefact upload
mendeni Jan 29, 2026
2d12cf7
Change mMaxRecvPaddingMs default from 2.0 to 0.0
mendeni Jan 29, 2026
775b63c
Comment out juce logging for most OSC operations
mendeni Jan 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,23 @@ jobs:
if: runner.os == 'Windows'
run: cmake -S . -B build -DJUCE_ENABLE_STANDALONE_APPLICATION:BOOL=ON -DJUCE_ASIO:BOOL=ON

- name: Configure CMake / Setup (Linux & macOS)
- name: Configure CMake (Linux & macOS)
if: runner.os != 'Windows'
run: |
./setupcmake.sh
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

- name: Build (Windows)
if: runner.os == 'Windows'
- name: Build
run: |
cmake --build build --config Release --target SonoBusMendeni_Standalone
cmake --build build --config Release --target SonoBusMendeni_VST3

- name: Build (Linux & macOS)
if: runner.os != 'Windows'
- name: Stage TouchOSC
run: |
./buildcmake.sh
cp util/SonoBusMendeni.tosc build/SonoBusMendeni_artefacts/Release

- name: Stage VST3 Install Script (macOS)
if: runner.os == 'macOS'
run: |
cp -a util/install-macos-vst3.sh build/SonoBusMendeni_artefacts/Release
cp util/install-macos-vst3.sh build/SonoBusMendeni_artefacts/Release

- name: Upload Sonobus artifacts (Windows & Linux)
if: runner.os != 'macOS'
Expand All @@ -56,6 +53,7 @@ jobs:
path: |
build/SonoBusMendeni_artefacts/Release/Standalone
build/SonoBusMendeni_artefacts/Release/VST3
build/SonoBusMendeni_artefacts/Release/SonoBusMendeni.tosc

- name: Upload Sonobus artifacts
if: runner.os == 'macOS'
Expand All @@ -66,3 +64,4 @@ jobs:
build/SonoBusMendeni_artefacts/Release/Standalone
build/SonoBusMendeni_artefacts/Release/VST3
build/SonoBusMendeni_artefacts/Release/install-macos-vst3.sh
build/SonoBusMendeni_artefacts/Release/SonoBusMendeni.tosc
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr
Source/MonitorDelayView.h
Source/OptionsView.cpp
Source/OptionsView.h
Source/OSCManager.cpp
Source/OSCManager.h
Source/ParametricEqView.h
Source/PeersContainerView.cpp
Source/PeersContainerView.h
Expand Down Expand Up @@ -614,6 +616,7 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr
juce::juce_dsp
juce::juce_cryptography
juce::juce_audio_plugin_client
juce::juce_osc

ff_meters

Expand Down
172 changes: 172 additions & 0 deletions Source/ChannelGroupsView.cpp

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Source/ChannelGroupsView.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public EffectsBaseView::HeaderListener

void effectsHeaderClicked(EffectsBaseView *comp) override;

// Public accessors for OSC control
MonitorDelayView* getDelayView() { return delayView.get(); }
ReverbSendView* getReverbSendView() { return reverbSendView.get(); }


int groupIndex = 0;
bool peerMode = false;
Expand Down Expand Up @@ -373,6 +377,13 @@ public MultiTimer
void applyToAllSliders(std::function<void(Slider *)> & routine);

void updateLayout(bool notify=true);

// Getters for OSC access to nested controls
ChannelGroupView* getFileChannelView() { return mFileChannelView.get(); }
ChannelGroupView* getSoundboardChannelView() { return mSoundboardChannelView.get(); }
ChannelGroupView* getMetChannelView() { return mMetChannelView.get(); }
TextButton* getInReverbButton() { return mInReverbButton.get(); }
TextButton* getMonDelayButton() { return mMonDelayButton.get(); }


std::function<AudioDeviceManager*()> getAudioDeviceManager; // = []() { return 0; };
Expand Down
20 changes: 20 additions & 0 deletions Source/MonitorDelayView.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class MonitorDelayView : public EffectsBaseView,
linkButton.setButtonText(TRANS("Link Delay Time with other inputs"));
linkButton.onClick = [this]() {
processor.setLinkMonitoringDelayTimes(linkButton.getToggleState());

// Send OSC message for link button state change (only for input groups >= 0)
if (processor.getOSCEnabled() && mGroupIndex >= 0) {
processor.getOSCManager().sendMessage("/InputGroup" + String(mGroupIndex + 1) + "MonDelayLink", linkButton.getToggleState() ? 1.0f : 0.0f);
}
};

// these are in the header component
Expand Down Expand Up @@ -189,6 +194,11 @@ class MonitorDelayView : public EffectsBaseView,
if (buttonThatWasClicked == &enableButton) {
mParams.enabled = enableButton.getToggleState();
headerComponent.repaint();

// Send OSC message for enable button state change (only for input groups >= 0)
if (processor.getOSCEnabled() && mGroupIndex >= 0) {
processor.getOSCManager().sendMessage("/InputGroup" + String(mGroupIndex + 1) + "MonDelayEnable", mParams.enabled ? 1.0f : 0.0f);
}
}
else if (buttonThatWasClicked == &autoButton) {
auto autoScalar = autoModeChoice.getSelectedItemIndex() == 1 ? 2.0f : 1.0f;
Expand All @@ -206,6 +216,11 @@ class MonitorDelayView : public EffectsBaseView,
mParams.enabled = !enableButton.getToggleState();
//processor.setMonitoringDelayActive(!enableButton.getToggleState());
updateParams(mParams);

// Send OSC message for enable button state change (only for input groups >= 0)
if (processor.getOSCEnabled() && mGroupIndex >= 0) {
processor.getOSCManager().sendMessage("/InputGroup" + String(mGroupIndex + 1) + "MonDelayEnable", mParams.enabled ? 1.0f : 0.0f);
}

listeners.call (&MonitorDelayView::Listener::monitorDelayParamsChanged, this, mParams);
}
Expand Down Expand Up @@ -249,6 +264,10 @@ class MonitorDelayView : public EffectsBaseView,
headerComponent.repaint();
}

// Setter for group index (needed for OSC addressing)
void setGroupIndex(int index) { mGroupIndex = index; }
int getGroupIndex() const { return mGroupIndex; }


private:

Expand All @@ -274,6 +293,7 @@ class MonitorDelayView : public EffectsBaseView,
FlexBox infoBox;

SonoAudio::DelayParams mParams;
int mGroupIndex = 0; // Group index for OSC addressing


JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MonitorDelayView)
Expand Down
123 changes: 123 additions & 0 deletions Source/OSCManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "OSCManager.h"

OSCManager::OSCManager()
{
// OSCManager object created, but not initialized until OSC is enabled
}

OSCManager::~OSCManager()
{
sender.disconnect();
disconnect();
}

// Initialize Receiver
bool OSCManager::initializeReceiver(int port)
{
if (!connect(port))
{
juce::Logger::writeToLog("Receiver failed to connect on port " + juce::String(port));
return false;
}

// Legacy listener - keeping for backward compatibility
addListener(this, "/volumeControl");
juce::Logger::writeToLog("Receiver connected on port " + juce::String(port));
return true;
}

// Initialize Sender
bool OSCManager::initializeSender(const juce::String& targetIP, int port)
{
senderConnected = sender.connect(targetIP, port);
if (!senderConnected)
{
juce::Logger::writeToLog("Sender failed to connect to " + targetIP + ":" + juce::String(port));
}
return senderConnected;
}

// Disconnect Receiver
void OSCManager::disconnectReceiver()
{
disconnect();
juce::Logger::writeToLog("OSC Receiver disconnected.");
}

// Disconnect Sender
void OSCManager::disconnectSender()
{
sender.disconnect();
senderConnected = false;
juce::Logger::writeToLog("OSC Sender disconnected.");
}

// Send Message
void OSCManager::sendMessage(const juce::String& address, const juce::var& value)
{
if (senderConnected)
{
if (value.isDouble())
{
// Apply OSC_SCALE_FACTOR to float/double values
sender.send(address, static_cast<float>(value) * OSC_SCALE_FACTOR);
}
else if (value.isInt())
{
sender.send(address, static_cast<int>(value));
}
else if (value.isString())
{
sender.send(address, value.toString());
}
else
{
juce::Logger::writeToLog("Unsupported var type in sendMessage for: " + address);
}
}
else
{
juce::Logger::writeToLog("OSC Sender is not connected! Cannot send message to: " + address);
}
}

// Register Control
void OSCManager::registerControl(const juce::String& address, ControlCallback callback)
{
controlRegistry[address] = callback;
// Also register as a listener for this OSC address pattern
addListener(this, address);
// juce::Logger::writeToLog("Registered control for OSC address: " + address);
}

// Unregister Control
void OSCManager::unregisterControl(const juce::String& address)
{
auto it = controlRegistry.find(address);
if (it != controlRegistry.end())
{
controlRegistry.erase(it);
// Note: JUCE doesn't provide a way to remove a specific listener,
// but the callback will no longer be called since it's removed from the registry
// juce::Logger::writeToLog("Unregistered control for OSC address: " + address);
}
}

// Handle Received Messages
void OSCManager::oscMessageReceived(const juce::OSCMessage& message)
{
juce::String address = message.getAddressPattern().toString();
// juce::Logger::writeToLog("Incoming OSC message: " + address);

// Check if a callback is registered for this address
auto it = controlRegistry.find(address);
if (it != controlRegistry.end())
{
// Call the registered callback
it->second(message);
}
else
{
juce::Logger::writeToLog("No handler registered for OSC address: " + address);
}
}
41 changes: 41 additions & 0 deletions Source/OSCManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <juce_osc/juce_osc.h>
#include <functional>
#include <map>

// Global constant for OSC message scaling factor
// Used to scale outbound float values for TouchOSC compatibility
constexpr float OSC_SCALE_FACTOR = 0.5f;

// Inverse scale factor for incoming OSC messages
// Used to compensate for the outbound scaling on specific controls
constexpr float OSC_INVERSE_SCALE_FACTOR = 2.0f;

class OSCManager : private juce::OSCReceiver,
private juce::OSCReceiver::ListenerWithOSCAddress<juce::OSCReceiver::MessageLoopCallback>
{
public:
OSCManager();
~OSCManager();

bool initializeReceiver(int port);
bool initializeSender(const juce::String& targetIP, int port);
void disconnectReceiver();
void disconnectSender();
void sendMessage(const juce::String& address, const juce::var& value);

// Generic control registration
using ControlCallback = std::function<void(const juce::OSCMessage&)>;
void registerControl(const juce::String& address, ControlCallback callback);
void unregisterControl(const juce::String& address);

private:
void oscMessageReceived(const juce::OSCMessage& message) override;

juce::OSCSender sender;
bool senderConnected = false; // Track sender connection state

// Control registry for dynamic OSC message handling
std::map<juce::String, ControlCallback> controlRegistry;
};
Loading