Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
38 changes: 38 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Pinned style for OALS. Tuned to match what's already in the tree
# (4-space indent, K&R braces, ref/pointer attached to the variable,
# no hard column cap). Apply to new code via editor-on-save; do NOT
# mass-format existing files in a single sweep.
---
BasedOnStyle: LLVM
Language: Cpp
Standard: c++20

IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100

BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
SpaceAfterCStyleCast: false
PointerAlignment: Left
ReferenceAlignment: Left
DerivePointerAlignment: false

AccessModifierOffset: -4
NamespaceIndentation: None
FixNamespaceComments: true

IncludeBlocks: Preserve
SortIncludes: false

AlignAfterOpenBracket: Align
BinPackParameters: false
BinPackArguments: true
AllowAllParametersOfDeclarationOnNextLine: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true

EmptyLineBeforeAccessModifier: LogicalBlock
SeparateDefinitionBlocks: Leave
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
push:
branches: ['**']
pull_request:
branches: ['**']

jobs:
build-test:
name: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install deps (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake ninja-build pkg-config \
qt6-base-dev libsndfile1-dev libgtest-dev

- name: Install deps (macOS)
if: runner.os == 'macOS'
run: |
brew update
brew install cmake ninja qt6 libsndfile googletest

- name: Configure
run: |
cmake -G Ninja -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DOAN_HOST_BACKENDS=ON \
-DCMAKE_PREFIX_PATH="$(brew --prefix qt6 2>/dev/null || echo /usr)"

- name: Build
run: cmake --build build

- name: Test
working-directory: build
run: ctest --output-on-failure
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/build/
23 changes: 23 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# pre-commit hooks for OALS. Install once per clone:
# pip install pre-commit && pre-commit install
# After that, every commit runs the hooks on staged files only.
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.6
hooks:
- id: clang-format
types_or: [c++, c]
# Exclude vendored / build trees and the OpenAudioNetwork submodule
# (it has its own .pre-commit-config.yaml and own clang-format
# invocation when committing inside it).
exclude: '^(build/|OpenAudioNetwork/|.*\.kicad_.*)'

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-yaml
- id: check-added-large-files
args: ['--maxkb=512']
40 changes: 37 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,53 @@ project(OALiveSystem)

set(CMAKE_CXX_STANDARD 20)

if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND NOT APPLE)
message("Added arm gcc optim")
add_compile_options(-ftree-vectorize)
endif()

set(ENGINE_PLUGIN_SYSLOCATION "/core_plugins")
option(OAN_HOST_BACKENDS "Build host dev transports + RT shim" OFF)
if(APPLE)
set(OAN_HOST_BACKENDS ON CACHE BOOL "Build host dev transports + RT shim" FORCE)
endif()
if(OAN_HOST_BACKENDS)
add_compile_definitions(OAN_HOST_BACKENDS)
endif()

# UID autoconfiguration: on by default for Linux production + host-backend
# dev. Off elsewhere (e.g. firmware-style toolchain where the build picks up
# this top-level CMake — unusual, but the firmwares vendor OAN directly and
# do their own flag wiring in phase 3).
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR OAN_HOST_BACKENDS)
set(_OAN_UID_AUTOCONF_DEFAULT ON)
else()
set(_OAN_UID_AUTOCONF_DEFAULT OFF)
endif()
option(OAN_UID_AUTOCONF "Enable UID autoconfiguration" ${_OAN_UID_AUTOCONF_DEFAULT})
if(OAN_UID_AUTOCONF)
add_compile_definitions(OAN_UID_AUTOCONF)
endif()
message(STATUS "OAN_UID_AUTOCONF = ${OAN_UID_AUTOCONF}")

if(APPLE)
# macOS root volume is read-only (SIP), so the Linux target's /core_plugins
# path can't be created. Use a per-user dir under $HOME instead — keeps
# the dev loop sudo-free.
set(ENGINE_PLUGIN_SYSLOCATION "$ENV{HOME}/.osst/core_plugins")
else()
set(ENGINE_PLUGIN_SYSLOCATION "/core_plugins")
endif()
add_compile_definitions(ENGINE_PLUGIN_SYSLOCATION="${ENGINE_PLUGIN_SYSLOCATION}")

enable_testing()

add_subdirectory(OpenDSP)
add_subdirectory(OpenAudioNetwork)

add_subdirectory(plugins)
add_subdirectory(coreui)
add_subdirectory(engine)
add_subdirectory(io_sim)
add_subdirectory(debugger)
add_subdirectory(debugger)
add_subdirectory(tools/sim_switch)
add_subdirectory(tools/oaninspect)
11 changes: 10 additions & 1 deletion coreui/core/NetworkConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@

struct NetworkConfig {
std::string eth_interface;
uint16_t uid;
// hint_uid: 0 = no hint, static-range value = pin (autoconfig skipped),
// dynamic-range value = ignored with a warning (per design §2.5).
uint16_t hint_uid = 0;
// persisted_uid: the autoconfigurator's last-committed value, fed back
// into the configurator as a "try this first" hint. Optional.
uint16_t persisted_uid = 0;

// After init_console runs, this is the committed UID (autoconfigured
// or static-pinned).
uint16_t uid = 0;

QJsonObject serialize();
};
Expand Down
139 changes: 134 additions & 5 deletions coreui/core/ShowManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,100 @@

#include "ShowManager.h"

#ifdef OAN_UID_AUTOCONF
#include "OpenAudioNetwork/common/UidStore.h"

#include <qjsonvalue.h>
#include <qsavefile.h>
#endif

#include <utility>

#ifdef OAN_UID_AUTOCONF
namespace {

// Persist the autoconfigured UID into a top-level field of an existing
// QJson document. Atomic via QSaveFile (write-tmp-then-rename).
class QJsonFieldUidStore : public IUidStore {
public:
QJsonFieldUidStore(QString path, QString field)
: m_path(std::move(path)), m_field(std::move(field)) {}

std::optional<uint16_t> load() override {
QFile f(m_path);
if (!f.open(QIODevice::ReadOnly)) return std::nullopt;
QJsonParseError err{};
auto doc = QJsonDocument::fromJson(f.readAll(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
std::cerr << "QJsonFieldUidStore: parse '" << m_path.toStdString()
<< "' failed: " << err.errorString().toStdString() << std::endl;
return std::nullopt;
}
auto root = doc.object();
auto net = root.value("network").toObject();
auto v = net.value(m_field);
if (!v.isDouble()) return std::nullopt;
int i = v.toInt(-1);
if (i < 0 || i > 0xFFFF) return std::nullopt;
return static_cast<uint16_t>(i);
}

void save(uint16_t uid) override {
QJsonObject root;
{
QFile f(m_path);
if (f.open(QIODevice::ReadOnly)) {
auto doc = QJsonDocument::fromJson(f.readAll());
if (doc.isObject()) root = doc.object();
}
}
QJsonObject net = root.value("network").toObject();
net.insert(m_field, static_cast<int>(uid));
root.insert("network", net);

QSaveFile out(m_path);
if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
std::cerr << "QJsonFieldUidStore: open '" << m_path.toStdString()
<< "' for write failed." << std::endl;
return;
}
out.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
if (!out.commit()) {
std::cerr << "QJsonFieldUidStore: commit '" << m_path.toStdString()
<< "' failed." << std::endl;
}
}

void clear() override {
QFile f(m_path);
if (!f.open(QIODevice::ReadOnly)) return;
auto doc = QJsonDocument::fromJson(f.readAll());
f.close();
if (!doc.isObject()) return;
auto root = doc.object();
auto net = root.value("network").toObject();
if (!net.contains(m_field)) return;
net.remove(m_field);
root.insert("network", net);

QSaveFile out(m_path);
if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
out.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
out.commit();
}

private:
QString m_path;
QString m_field;
};

bool is_static_range(uint16_t uid) {
return uid >= 0xF000 && uid <= 0xFFFE;
}

} // namespace
#endif

ShowManager::ShowManager() : QObject(nullptr) {
m_netconfig = NetworkConfig{};
}
Expand All @@ -22,7 +114,15 @@ bool ShowManager::init_console(SignalWindow* sw) {
infos.dev_type = DeviceType::CONTROL_SURFACE;
infos.iface = m_netconfig.eth_interface;
infos.sample_rate = SamplingRate::SAMPLING_96K;
infos.uid = m_netconfig.uid;
#ifdef OAN_UID_AUTOCONF
// Hint = static-range pin only; dynamic-range hints are ignored
// by the configurator (per design §2.5).
infos.uid = is_static_range(m_netconfig.hint_uid) ? m_netconfig.hint_uid : 0;
#else
// Pre-autoconfig path: honour whatever was in the config, fall back
// to the historical 200 if nothing was set.
infos.uid = m_netconfig.hint_uid != 0 ? m_netconfig.hint_uid : 200;
#endif
infos.topo.phy_in_count = 0;
infos.topo.phy_out_count = 0;
infos.topo.pipes_count = 0;
Expand All @@ -34,6 +134,33 @@ bool ShowManager::init_console(SignalWindow* sw) {
return false;
}

#ifdef OAN_UID_AUTOCONF
if (!is_static_range(m_netconfig.hint_uid)) {
// No static pin — run autoconfig, persist to surface.json.
auto backing = std::make_unique<QJsonFieldUidStore>(
QStringLiteral("surface_config/surface.json"),
QStringLiteral("persisted_uid"));
if (m_renumber) {
std::cout << "--renumber: clearing persisted UID in surface.json" << std::endl;
backing->clear();
}
EnvOverrideUidStore store{"OAN_PERSISTED_UID", std::move(backing)};
uint16_t committed = m_nmapper->autoconfigure_uid(store);
if (committed == 0) {
std::cerr << "ShowManager: UID autoconfiguration failed." << std::endl;
return false;
}
m_netconfig.uid = committed;
} else {
m_netconfig.uid = m_netconfig.hint_uid;
std::cout << "ShowManager: static-range UID 0x" << std::hex
<< m_netconfig.hint_uid << std::dec
<< " pinned; autoconfig skipped." << std::endl;
}
#else
m_netconfig.uid = infos.uid;
#endif

std::cout << "Starting netmapper and router processes on interface " << infos.iface << std::endl;

m_nmapper->set_peer_change_callback([this](PeerInfos& infos, bool peer_state) {
Expand Down Expand Up @@ -155,21 +282,23 @@ void ShowManager::load_console_config() {

if (!config_file.open(QIODeviceBase::ReadOnly)) {
std::cerr << "Failed to open surface.json config file" << std::endl;
std::cerr << "Using default config (iface = lo, uid = 200)" << std::endl;
std::cerr << "Using default config (iface = lo, no UID hint)" << std::endl;

NetworkConfig netcfg{};
netcfg.eth_interface = "lo";
netcfg.uid = 200;

netcfg.hint_uid = 0;
netcfg.persisted_uid = 0;
m_netconfig = std::move(netcfg);
return;
}

auto doc = QJsonDocument::fromJson(config_file.readAll());
QJsonObject net_root = doc["network"].toObject();

NetworkConfig netcfg{};
netcfg.eth_interface = net_root["eth_interface"].toString("lo").toStdString();
netcfg.uid = net_root["uid"].toInt(200);
netcfg.hint_uid = static_cast<uint16_t>(net_root["uid"].toInt(0));
netcfg.persisted_uid = static_cast<uint16_t>(net_root["persisted_uid"].toInt(0));

m_netconfig = std::move(netcfg);
}
Expand Down
5 changes: 5 additions & 0 deletions coreui/core/ShowManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class ShowManager : public QObject {

bool init_console(SignalWindow* sw);

// Set before init_console: if true, the autoconfigurator clears any
// persisted UID before deriving a fresh one.
void set_renumber(bool r) { m_renumber = r; }

void add_pipe(PipeDesc *pipe_desc, QString pipe_name, uint8_t channel, uint16_t host, uint16_t pid,
bool unsynced = false);
void update_page(SignalWindow* swin);
Expand Down Expand Up @@ -74,6 +78,7 @@ class ShowManager : public QObject {

std::shared_ptr<NetworkMapper> m_nmapper;
NetworkConfig m_netconfig;
bool m_renumber = false;

DSPManager* m_dsp_manager;
std::shared_ptr<PluginLoader> m_plugin_loader;
Expand Down
Loading
Loading