chore: remove skeleton build mode and stub #ifdef scaffolding
Some checks failed
Build Linux Binaries / linux/amd64 (push) Has been cancelled
Build Linux Binaries / linux/arm64 (push) Has been cancelled

Drop the M0 no-deps skeleton preset and all VOICECAT_HAS_NET/AUDIO/OPUS/NS
guards that it required. Every subsystem is fully implemented; the stub
#else paths were dead code that added noise to every header and source file.

- CMakePresets.json: remove skeleton configure/build/test entries
- CMakeLists.txt (root/core/tests): remove VOICECAT_USE_VCPKG_DEPS option
  and guards; all targets now build unconditionally
- 17 C++ source files: unwrap HAS_* guards, delete stub #else blocks
- apm_processor.cpp: delete ApmPassthrough no-op class; create() always
  returns RnnoiseProcessor
- 18 test files: remove HAS_* guards and stub int main() skip bodies
- docs/building.md: remove skeleton from preset table and prose

VOICECAT_HAS_LOOPBACK (Windows WASAPI loopback platform gate) unchanged.
29/29 ctest green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 11:32:22 +01:00
parent 9a20953c08
commit 2c8178fa02
55 changed files with 25 additions and 675 deletions

View File

@@ -42,8 +42,7 @@ set_target_properties(voicecat PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON)
if(VOICECAT_USE_VCPKG_DEPS)
find_package(protobuf CONFIG REQUIRED)
find_package(protobuf CONFIG REQUIRED)
find_package(unofficial-sodium CONFIG REQUIRED)
find_package(MbedTLS CONFIG REQUIRED)
find_package(asio CONFIG REQUIRED)
@@ -129,4 +128,3 @@ if(VOICECAT_USE_VCPKG_DEPS)
# Signal to C++ code that the real networking/crypto stack is available.
target_compile_definitions(voicecat PUBLIC VOICECAT_HAS_NET)
endif()

View File

@@ -8,13 +8,9 @@
* Design: docs/architecture.md §4. Everything here is async + event-driven — calls return
* immediately and results/state changes arrive via the vc_callbacks.on_event callback.
*
* STATUS: real, behind VOICECAT_HAS_NET (the `dev`/`release`/`server-release` presets —
* vcpkg deps on; see docs/building.md). As of M3, control plane, voice, multi-stream, device
* enumeration, VAD/PTT, and stereo playback all work for real via core/src/core/client.cpp.
* The no-deps `skeleton` preset still links a stub vc_client that returns VC_ERR_NOT_IMPLEMENTED
* for everything below `connect`, purely to keep that skeleton build green. webrtc AEC/NS/AGC
* remains an inert passthrough regardless of preset (no Windows/MSVC port upstream —
* docs/voice.md §8/§11, PROGRESS.md).
* STATUS: real. Control plane, voice, multi-stream, device enumeration, VAD/PTT, and stereo
* playback all work via core/src/core/client.cpp. webrtc AEC/NS/AGC remains an inert passthrough
* (no Windows/MSVC port upstream — docs/voice.md §8/§11, PROGRESS.md).
*/
#ifndef VOICECAT_H
#define VOICECAT_H
@@ -56,7 +52,7 @@ extern "C" {
/* ── Result codes ─────────────────────────────────────────────────────────── */
typedef enum vc_result {
VC_OK = 0,
VC_ERR_NOT_IMPLEMENTED = 1, /* skeleton stub */
VC_ERR_NOT_IMPLEMENTED = 1,
VC_ERR_INVALID_ARG = 2,
VC_ERR_NOT_CONNECTED = 3,
VC_ERR_ALREADY = 4,

View File

@@ -5,9 +5,7 @@
#include <chrono>
#include <cmath>
#ifdef VOICECAT_HAS_NS
#include "rnnoise.h"
#endif
namespace voicecat::audio {
@@ -19,15 +17,6 @@ int64_t steady_now_ms() {
}
} // namespace
// ── ApmPassthrough ────────────────────────────────────────────────────────────
// No-op: returns true (VAD always open), does not modify PCM.
// Replaced by WebrtcApmProcessor when VOICECAT_HAS_APM is defined.
class ApmPassthrough final : public ApmProcessor {
public:
void process_render(const int16_t*, int, int) override {}
bool process_capture(int16_t*, int, int) override { return true; }
};
// ── EnergyVadProcessor ──────────────────────────────────────────────────────
// Lightweight, dependency-free energy/RMS VAD — see apm_processor.h's create_vad() doc comment
// for why this exists instead of a real APM. No AEC (process_render is a no-op); doesn't modify
@@ -62,7 +51,6 @@ class EnergyVadProcessor final : public ApmProcessor {
int64_t last_voice_ms_ = 0; // epoch start -> gate begins closed until first loud frame
};
#ifdef VOICECAT_HAS_NS
// ── RnnoiseProcessor ─────────────────────────────────────────────────────────
// Real noise suppression via vendored RNNoise (third_party/rnnoise; docs/voice.md §10-11).
// RNNoise is a mono, 48 kHz, fixed 480-sample (10 ms) speech denoiser; our engine clock is fixed
@@ -103,14 +91,9 @@ class RnnoiseProcessor final : public ApmProcessor {
float in_[kFrame];
float out_[kFrame];
};
#endif // VOICECAT_HAS_NS
std::unique_ptr<ApmProcessor> ApmProcessor::create() {
#ifdef VOICECAT_HAS_NS
return std::make_unique<RnnoiseProcessor>();
#else
return std::make_unique<ApmPassthrough>();
#endif
}
std::unique_ptr<ApmProcessor> ApmProcessor::create_vad(float rms_threshold,

View File

@@ -1,7 +1,5 @@
#ifdef VOICECAT_HAS_AUDIO
#define MINIAUDIO_IMPLEMENTATION
#include <miniaudio.h>
#endif
#include "audio/audio_engine.h"
@@ -41,7 +39,6 @@ constexpr int32_t kStarveSamples = 48000 * 120 / 1000; // reseed when clock
// defense-in-depth against any future regression that skips remove_stream.
constexpr int32_t kPlcCapSamples = 48000 * 2; // 2 s @ 48 kHz
#ifdef VOICECAT_HAS_AUDIO
// device_id encoding (DeviceInfo::id / AudioParams::*_device_id): a hex string of the raw
// ma_device_id bytes. Opaque on purpose — names aren't guaranteed unique, and this is the only
// stable handle miniaudio accepts back for device selection. Internal contract only; never
@@ -76,7 +73,6 @@ bool hex_decode_device_id(const std::string& hex, ma_device_id* out) {
}
return true;
}
#endif // VOICECAT_HAS_AUDIO
} // namespace
// ── JitterBuffer ─────────────────────────────────────────────────────────────
@@ -197,19 +193,14 @@ AudioEngine::AudioEngine() = default;
AudioEngine::~AudioEngine() {
stop();
#ifdef VOICECAT_HAS_AUDIO
stop_loopback_capture(); // loopback has an independent lifecycle — close it before the context
if (context_inited_) {
ma_context_uninit(&context_);
context_inited_ = false;
}
#endif
#ifdef VOICECAT_HAS_OPUS
if (dred_dec_) { opus_dred_decoder_destroy(dred_dec_); dred_dec_ = nullptr; }
#endif
}
#ifdef VOICECAT_HAS_AUDIO
ma_context_config AudioEngine::make_context_config() {
ma_context_config cfg = ma_context_config_init();
// iOS: leave AVAudioSession entirely to the Swift layer (IOSAudioRouter). See the
@@ -219,7 +210,6 @@ ma_context_config AudioEngine::make_context_config() {
cfg.coreaudio.noAudioSessionDeactivate = MA_TRUE; // don't setActive(false) on device uninit
return cfg;
}
#endif
bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
if (running_.load()) return false;
@@ -227,14 +217,11 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
capture_cb_ = std::move(capture_cb);
frame_samples_ = static_cast<int>(p.sample_rate / 1000 * p.frame_ms);
running_.store(true, std::memory_order_release);
#ifdef VOICECAT_HAS_OPUS
if (!dred_dec_) {
int err = 0;
dred_dec_ = opus_dred_decoder_create(&err); // null on failure — DRED silently disabled
}
#endif
#ifdef VOICECAT_HAS_AUDIO
// Pre-allocate capture accumulators before the devices start so on_capture / on_loopback
// never allocate on the RT thread. count=0 means "empty"; the buf is sized to exactly one
// encoder frame so a memcpy into it can never overrun. The mic accumulator is sized to
@@ -247,9 +234,7 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
capture_accum_.count = 0;
loopback_accum_.buf.assign(static_cast<size_t>(frame_samples_), 0);
loopback_accum_.count = 0;
#endif
#ifdef VOICECAT_HAS_AUDIO
// Own the ma_context (lazily, reused across restarts) so miniaudio does not touch
// AVAudioSession on iOS — the Swift IOSAudioRouter is the sole session owner. Without
// this, ma_device_init(nullptr, ...) below would reset the session category to Record/
@@ -342,14 +327,12 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
mixer_timer_stop_.store(false, std::memory_order_release);
mixer_timer_thread_ = std::thread([this] { run_mixer_timer(); });
}
#endif // VOICECAT_HAS_AUDIO
return true;
}
std::vector<DeviceInfo> AudioEngine::enumerate_devices(bool capture) {
std::vector<DeviceInfo> result;
#ifdef VOICECAT_HAS_AUDIO
// Use the no-AVAudioSession-management config here too: device enumeration runs at
// SessionState init (refreshDevices) and on settings views, and a default-config context
// would call setCategory()/setActive() on iOS, disrupting the session the Swift layer owns.
@@ -377,16 +360,12 @@ std::vector<DeviceInfo> AudioEngine::enumerate_devices(bool capture) {
}
ma_context_uninit(&ctx);
#else
(void)capture;
#endif // VOICECAT_HAS_AUDIO
return result;
}
void AudioEngine::stop() {
if (!running_.exchange(false)) return;
#ifdef VOICECAT_HAS_AUDIO
// External playback: stop + join the mixer-timer thread before tearing down state it reads.
mixer_timer_stop_.store(true, std::memory_order_release);
if (mixer_timer_thread_.joinable()) mixer_timer_thread_.join();
@@ -400,11 +379,9 @@ void AudioEngine::stop() {
ma_device_uninit(&playback_device_);
playback_started_ = false;
}
#endif
}
bool AudioEngine::suspend() {
#ifdef VOICECAT_HAS_AUDIO
if (!running_.load(std::memory_order_acquire)) return true;
// External playback: pause the mixer timer (there is no playback device to stop). This
// matches the VPIO renderer going down during an AVAudioSession interruption.
@@ -416,13 +393,9 @@ bool AudioEngine::suspend() {
if (capture_started_) ok &= (ma_device_stop(&capture_device_) == MA_SUCCESS);
if (playback_started_) ok &= (ma_device_stop(&playback_device_) == MA_SUCCESS);
return ok;
#else
return true;
#endif
}
bool AudioEngine::resume() {
#ifdef VOICECAT_HAS_AUDIO
if (!running_.load(std::memory_order_acquire)) return true;
// External playback: relaunch the mixer timer (mixer_scratch_ is still sized from start()).
if (external_playback_ && !mixer_timer_thread_.joinable()) {
@@ -433,9 +406,6 @@ bool AudioEngine::resume() {
if (capture_started_) ok &= (ma_device_start(&capture_device_) == MA_SUCCESS);
if (playback_started_) ok &= (ma_device_start(&playback_device_) == MA_SUCCESS);
return ok;
#else
return true;
#endif
}
void AudioEngine::inject_capture(int kind, const int16_t* pcm, size_t samples_per_channel,
@@ -530,12 +500,10 @@ void AudioEngine::remove_stream(uint32_t ssrc) {
std::lock_guard lk(streams_mu_);
auto it = streams_.find(ssrc);
if (it != streams_.end()) {
#ifdef VOICECAT_HAS_OPUS
if (it->second.dred_state_) {
opus_dred_free(it->second.dred_state_);
it->second.dred_state_ = nullptr;
}
#endif
streams_.erase(it);
}
}
@@ -590,7 +558,6 @@ int32_t AudioEngine::stream_playout_depth_samples(uint32_t ssrc) const {
return static_cast<int32_t>(*newest - it->second.playout_ts);
}
#ifdef VOICECAT_HAS_OPUS
void AudioEngine::init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
uint32_t user_id, uint32_t stream_id, bool is_voice) {
std::lock_guard lk(streams_mu_);
@@ -616,7 +583,6 @@ void AudioEngine::init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
stream.dred_state_ = opus_dred_alloc(&err); // null on failure — falls back to PLC
}
}
#endif
void AudioEngine::set_pcm_sink(PcmSink cb, void* user) {
pcm_sink_user_.store(user, std::memory_order_relaxed);
@@ -628,8 +594,6 @@ void AudioEngine::set_mixed_output_sink(MixedSink cb, void* user) {
mixed_sink_.store(cb, std::memory_order_release);
}
#ifdef VOICECAT_HAS_AUDIO
void AudioEngine::capture_data_cb(ma_device* dev, void* /*out*/,
const void* in, ma_uint32 frame_count) {
auto* self = static_cast<AudioEngine*>(dev->pUserData);
@@ -678,7 +642,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
std::unique_lock lk(streams_mu_, std::try_to_lock);
if (!lk) return; // contended: emit silence this period
#ifdef VOICECAT_HAS_OPUS
std::vector<int32_t> mix(frames * pb_channels, 0);
for (auto& [ssrc, stream] : streams_) {
@@ -747,7 +710,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
// embeds in the next packet) → PLC comfort noise. DRED and FEC both need the
// *next* packet already buffered, so copy it once and try each in turn.
n = -1;
#ifdef VOICECAT_HAS_OPUS
uint32_t next_ts = stream.playout_ts + static_cast<uint32_t>(frame_samples);
size_t psz = stream.jitter.try_copy_front_payload(
next_ts, stream.dred_payload_scratch_.data(),
@@ -777,7 +739,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
stream.dred_payload_scratch_.data(), static_cast<int>(psz),
stream.decode_scratch.data(), frame_samples, /*fec=*/true);
}
#endif
// 3. PLC: synthesize a continuation when no redundancy is available.
if (n <= 0) {
n = stream.decoder.decode(nullptr, 0, stream.decode_scratch.data(),
@@ -858,10 +819,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
int32_t s = static_cast<int32_t>(static_cast<float>(mix[i]) * ovol);
out[i] = static_cast<int16_t>(std::clamp(s, -32768, 32767));
}
#else
(void)out;
(void)frames;
#endif
}
// External-playback timer (iOS VPIO): with no hardware playback device to "pull" frames, this
@@ -986,11 +943,4 @@ bool AudioEngine::start_loopback_capture(int /*kind*/, int /*channels*/) { retur
void AudioEngine::stop_loopback_capture() {}
#endif // VOICECAT_HAS_LOOPBACK
#endif // VOICECAT_HAS_AUDIO
#ifndef VOICECAT_HAS_AUDIO
bool AudioEngine::start_loopback_capture(int /*kind*/, int /*channels*/) { return false; }
void AudioEngine::stop_loopback_capture() {}
#endif
} // namespace voicecat::audio

View File

@@ -25,14 +25,10 @@
#include <unordered_map>
#include <vector>
#ifdef VOICECAT_HAS_AUDIO
// miniaudio single-header — MINIAUDIO_IMPLEMENTATION defined in audio_engine.cpp
#include <miniaudio.h>
#endif
#ifdef VOICECAT_HAS_OPUS
#include "codec/opus_codec.h"
#endif
#include "audio/apm_processor.h"
@@ -222,7 +218,6 @@ class AudioEngine {
// bounded-depth invariant. Returns 0 if the stream is unknown or not yet playing out.
int32_t stream_playout_depth_samples(uint32_t ssrc) const;
#ifdef VOICECAT_HAS_OPUS
// Configure the Opus decoder for an incoming ssrc (must be called before
// push_recv_frame for that ssrc). user_id/stream_id identify the source for the
// pcm_sink_ callback. is_voice marks a MIC stream so the receive-side NR pass knows it may
@@ -230,7 +225,6 @@ class AudioEngine {
// Thread-safe.
void init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
uint32_t user_id, uint32_t stream_id, bool is_voice);
#endif
// External PCM tap: callback fired once per decoded Opus frame per remote stream, on the
// playback (RT) thread. Matching signature to vc_pcm_sink_cb (cast at the C-ABI boundary).
@@ -254,7 +248,6 @@ class AudioEngine {
// is samples per channel; total samples written = samples_per_channel * channels.
void inject_capture(int kind, const int16_t* pcm, size_t samples_per_channel, int channels);
#ifdef VOICECAT_HAS_AUDIO
// TEST-ONLY — exposes the playback mixer without a real ma_device, so tests can verify
// stereo mixing end-to-end (no audio hardware needed). Same logic the real playback
// callback uses; safe to call any time after start() (no ma_device touched).
@@ -312,7 +305,6 @@ class AudioEngine {
}
}
}
#endif
#ifdef VOICECAT_HAS_LOOPBACK
// TEST-ONLY — drives the loopback accumulator directly with an explicit callback, the
@@ -354,7 +346,6 @@ class AudioEngine {
#endif
private:
#ifdef VOICECAT_HAS_AUDIO
static void capture_data_cb(ma_device*, void*, const void*, ma_uint32);
static void playback_data_cb(ma_device*, void*, const void*, ma_uint32);
void on_capture(const int16_t* pcm, ma_uint32 frames);
@@ -403,7 +394,6 @@ class AudioEngine {
bool loopback_started_ = false;
int loopback_kind_ = 0;
int loopback_channels_ = 1; // channel count the loopback device was opened with
#endif
#endif
AudioParams params_{};
@@ -441,9 +431,7 @@ class AudioEngine {
// Per remote stream (protected by streams_mu_).
struct RemoteStream {
JitterBuffer jitter;
#ifdef VOICECAT_HAS_OPUS
codec::OpusDecoder decoder;
#endif
float gain = 1.0f;
bool mute = false;
uint32_t playout_ts = 0;
@@ -484,9 +472,7 @@ class AudioEngine {
// DRED: pre-allocated scratch for loss recovery. dred_state_ is per-stream; see
// AudioEngine::dred_dec_ (shared). Allocated in init_recv_stream(); freed in remove_stream().
#ifdef VOICECAT_HAS_OPUS
::OpusDRED* dred_state_ = nullptr;
#endif
std::vector<uint8_t> dred_payload_scratch_; // pre-sized to 4000 bytes
// In-band FEC: whether the sender negotiated OPUS_SET_INBAND_FEC for this stream.
@@ -574,9 +560,7 @@ class AudioEngine {
std::atomic<void*> mixed_sink_user_{nullptr};
bool external_playback_ = false;
#ifdef VOICECAT_HAS_OPUS
::OpusDREDDecoder* dred_dec_ = nullptr; // shared DRED decoder; null if unsupported
#endif
static constexpr int64_t kTalkHangoverMs = 300;
};

View File

@@ -2,8 +2,6 @@
namespace voicecat::codec {
#ifdef VOICECAT_HAS_OPUS
// Map an intended channel/capture sample rate (Hz) to the Opus max-bandwidth constant. The
// codec always runs at 48 kHz internally (docs/voice.md §3); this caps the bandwidth the
// encoder will select so a channel can request narrowband/wideband audio for low-bitrate rooms.
@@ -103,16 +101,4 @@ void OpusDecoder::destroy() {
if (dec_) { opus_decoder_destroy(dec_); dec_ = nullptr; }
}
#else // !VOICECAT_HAS_OPUS — stubs
bool OpusEncoder::init(const OpusParams&) { err_ = "OPUS not compiled in"; return false; }
int OpusEncoder::encode(const int16_t*, int, uint8_t*, int) { return -1; }
void OpusEncoder::destroy() {}
bool OpusDecoder::init(const OpusParams&) { err_ = "OPUS not compiled in"; return false; }
int OpusDecoder::decode(const uint8_t*, int, int16_t*, int, bool) { return -1; }
void OpusDecoder::destroy() {}
#endif // VOICECAT_HAS_OPUS
} // namespace voicecat::codec

View File

@@ -10,9 +10,7 @@
#include <cstdint>
#include <vector>
#ifdef VOICECAT_HAS_OPUS
#include <opus/opus.h>
#endif
namespace voicecat::codec {
@@ -73,11 +71,7 @@ class OpusEncoder {
const char* error_string() const { return err_; }
private:
#ifdef VOICECAT_HAS_OPUS
::OpusEncoder* enc_ = nullptr;
#else
void* enc_ = nullptr;
#endif
int frame_samples_ = 0;
int channels_ = 1;
const char* err_ = nullptr;
@@ -104,10 +98,8 @@ class OpusDecoder {
// Decode a lost frame using pre-parsed DRED state from the next received packet.
// dred_offset=0 means the frame immediately before the next packet.
// Returns frame_samples on success, -1 if DRED unavailable or decode failed.
#ifdef VOICECAT_HAS_OPUS
int decode_dred(::OpusDRED* dred, int32_t dred_offset, int16_t* out_pcm, int max_samples);
::OpusDecoder* raw() const { return dec_; }
#endif
void destroy();
@@ -117,11 +109,7 @@ class OpusDecoder {
const char* error_string() const { return err_; }
private:
#ifdef VOICECAT_HAS_OPUS
::OpusDecoder* dec_ = nullptr;
#else
void* dec_ = nullptr;
#endif
int frame_samples_ = 0;
int channels_ = 1;
const char* err_ = nullptr;

View File

@@ -1,7 +1,5 @@
#include "core/client.h"
#ifdef VOICECAT_HAS_NET
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
@@ -1867,7 +1865,6 @@ vc_result vc_client::test_inject_capture(uint32_t stream_id, const int16_t* pcm,
}
vc_result vc_client::list_devices(vc_device_kind kind, vc_device_list* out) {
#ifdef VOICECAT_HAS_AUDIO
// Works in any connection state — device pickers need to populate pre-connect.
auto devices = voicecat::audio::AudioEngine::enumerate_devices(kind == VC_DEVICE_INPUT);
@@ -1885,12 +1882,6 @@ vc_result vc_client::list_devices(vc_device_kind kind, vc_device_list* out) {
out->items = items;
out->count = devices.size();
return VC_OK;
#else
(void)kind;
out->items = nullptr;
out->count = 0;
return VC_ERR_NOT_IMPLEMENTED;
#endif
}
// ── M4: channel/user/stream snapshot getters ─────────────────────────────────
@@ -2209,102 +2200,3 @@ void vc_client::run_talk_timer() {
std::this_thread::sleep_for(std::chrono::milliseconds(kTalkPollMs));
}
}
#else // !VOICECAT_HAS_NET
// ── M0 stub implementations ───────────────────────────────────────────────────
vc_client::vc_client(const vc_config& cfg, vc_callbacks cb) : cfg_(cfg), cb_(cb) {}
vc_client::~vc_client() = default;
void vc_client::emit(const vc_event& ev) const {
if (cb_.on_event) cb_.on_event(cb_.user, &ev);
}
vc_result vc_client::connect(const char*, uint16_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::disconnect() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::authenticate_guest(const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::authenticate_user(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::join_channel(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::leave_channel() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::join_voice() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::leave_voice() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::stream_start(const vc_stream_desc&, uint32_t*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::stream_stop(uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_input_device(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_capture_channels(uint32_t, uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_input_mode(vc_input_mode) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_vad_threshold(float) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_input_gain(float) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_input_noise_reduction(bool) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_push_to_talk(bool) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_self_mute(bool, bool) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_remote_stream(uint32_t, uint32_t, float, bool, bool) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::get_remote_stream(uint32_t, uint32_t, vc_remote_stream_state*) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::send_text(vc_text_scope, uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::list_devices(vc_device_kind, vc_device_list* out) {
out->items = nullptr;
out->count = 0;
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::get_stream_audio_config(uint32_t, uint32_t, vc_audio_config*) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::stream_feed_pcm(uint32_t, const int16_t*, size_t, uint32_t) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::set_pcm_sink(vc_pcm_sink_cb, void*) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::set_mixed_output_sink(vc_mixed_output_cb, void*) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::set_external_playback(bool) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::test_inject_capture(uint32_t, const int16_t*, size_t) {
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::list_channels(vc_channel_list* out) {
out->items = nullptr;
out->count = 0;
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::list_users(vc_user_list* out) {
out->items = nullptr;
out->count = 0;
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::list_user_streams(uint32_t, vc_stream_summary_list* out) {
out->items = nullptr;
out->count = 0;
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::confirm_server_identity(bool) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::get_server_identity_display(char*, size_t, size_t* out_len) {
if (out_len) *out_len = 0;
return VC_ERR_NOT_IMPLEMENTED;
}
vc_result vc_client::kick_user(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::ban_user(uint32_t, const char*, uint64_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_permission(uint32_t, const vc_permissions*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::set_server_mute(uint32_t, bool, bool) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::move_user(uint32_t, uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::create_channel(const vc_channel_info*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::edit_channel(const vc_channel_info*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::delete_channel(uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::create_account(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::reset_password(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::delete_account(const char*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::list_accounts() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::get_account_list(vc_account_list*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::get_permissions(vc_permissions*) { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::audio_suspend() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::audio_resume() { return VC_ERR_NOT_IMPLEMENTED; }
vc_result vc_client::audio_restart() { return VC_ERR_NOT_IMPLEMENTED; }
#endif // VOICECAT_HAS_NET

View File

@@ -6,8 +6,6 @@
#include "voicecat.h"
#ifdef VOICECAT_HAS_NET
#include <atomic>
#include <condition_variable>
#include <deque>
@@ -29,8 +27,6 @@
#include "session/session.h"
#include "proto/voicecat.pb.h"
#endif // VOICECAT_HAS_NET
struct vc_client {
vc_client(const vc_config& cfg, vc_callbacks cb);
~vc_client();
@@ -118,11 +114,7 @@ struct vc_client {
vc_result get_permissions(vc_permissions* out);
vc_connection_state state() const {
#ifdef VOICECAT_HAS_NET
return state_net_.load(std::memory_order_acquire);
#else
return state_;
#endif
}
private:
@@ -131,7 +123,6 @@ struct vc_client {
vc_config cfg_{};
vc_callbacks cb_{};
#ifdef VOICECAT_HAS_NET
// ── M1: TCP/TLS control channel ─────────────────────────────────────────────
std::atomic<vc_connection_state> state_net_{VC_STATE_DISCONNECTED};
@@ -339,12 +330,8 @@ struct vc_client {
// capture callback never allocates or races this pointer.
std::unique_ptr<voicecat::audio::ApmProcessor> mic_ns_;
// teardown_voice() is called both from run_io()'s own cleanup (on the io_thread_, when
// the read loop exits) and from disconnect() (on the caller's thread) -- without
// serializing those two call sites, both can see udp_thread_/talk_timer_thread_ as
// joinable() at the same time and race to join() the same std::thread object (UB; an
// intermittent "No such process" std::system_error on Windows is the typical symptom).
// This mutex makes teardown_voice() idempotent under concurrent calls.
// teardown_voice() is called from run_io() and disconnect() concurrently; this mutex
// makes it idempotent (avoids a double-join race on udp_thread_/talk_timer_thread_).
std::mutex teardown_mu_;
// ── io_thread_ entry point ──────────────────────────────────────────────────
@@ -432,10 +419,6 @@ struct vc_client {
// Convenience event emitters.
void emit_error(vc_result r, const char* text);
void emit_disconnected(vc_result r, const char* reason);
#else // !VOICECAT_HAS_NET
vc_connection_state state_{VC_STATE_DISCONNECTED};
#endif
};
#endif // VOICECAT_CORE_CLIENT_H

View File

@@ -4,13 +4,10 @@
* Used for Argon2id password hashing (deliberately slow) and TLS handshakes so
* neither blocks the net thread. Real-time audio threads never use this.
*
* Requires VOICECAT_HAS_NET (Asio). Undefined when building without deps.
*/
#ifndef VOICECAT_CORE_WORKER_POOL_H
#define VOICECAT_CORE_WORKER_POOL_H
#ifdef VOICECAT_HAS_NET
#include <asio/thread_pool.hpp>
#include <asio/post.hpp>
#include <cstddef>
@@ -37,5 +34,4 @@ class WorkerPool {
} // namespace voicecat
#endif // VOICECAT_HAS_NET
#endif // VOICECAT_CORE_WORKER_POOL_H

View File

@@ -1,7 +1,5 @@
#include "crypto/crypto.h"
#ifdef VOICECAT_HAS_NET
#include <cstring>
#include <fstream>
#include <sstream>
@@ -406,5 +404,3 @@ long SodiumMediaCrypto::open(const uint8_t* sealed, size_t len, const uint8_t* a
}
} // namespace voicecat::crypto
#endif // VOICECAT_HAS_NET

View File

@@ -12,8 +12,6 @@
#include <cstdint>
#include <string>
#ifdef VOICECAT_HAS_NET
#include <array>
#include <filesystem>
#include <functional>
@@ -186,25 +184,4 @@ class SodiumMediaCrypto final : public MediaCrypto {
} // namespace voicecat::crypto
#else // !VOICECAT_HAS_NET — skeleton stubs
namespace voicecat::crypto {
class MediaCrypto {
public:
virtual ~MediaCrypto() = default;
virtual long seal(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0;
virtual long open(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0;
};
class SodiumMediaCrypto final : public MediaCrypto {
public:
long seal(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) override { return -1; }
long open(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) override { return -1; }
uint64_t peek_send_counter() const { return 0; }
};
} // namespace voicecat::crypto
#endif // VOICECAT_HAS_NET
#endif // VOICECAT_CRYPTO_CRYPTO_H

View File

@@ -1,7 +1,5 @@
#include "crypto/tofu_store.h"
#ifdef VOICECAT_HAS_NET
#include <fstream>
#include <sstream>
#include <stdexcept>
@@ -93,5 +91,3 @@ void TofuStore::save() const {
}
} // namespace voicecat::crypto
#endif // VOICECAT_HAS_NET

View File

@@ -7,8 +7,6 @@
#ifndef VOICECAT_CRYPTO_TOFU_STORE_H
#define VOICECAT_CRYPTO_TOFU_STORE_H
#ifdef VOICECAT_HAS_NET
#include <array>
#include <filesystem>
#include <mutex>
@@ -62,5 +60,4 @@ class TofuStore {
} // namespace voicecat::crypto
#endif // VOICECAT_HAS_NET
#endif // VOICECAT_CRYPTO_TOFU_STORE_H

View File

@@ -1,7 +1,5 @@
#include "net/transport.h"
#ifdef VOICECAT_HAS_NET
#include <cstring>
#include "crypto/crypto.h"
@@ -483,5 +481,3 @@ asio::ip::udp::endpoint UdpMediaChannel::local_endpoint() const {
}
} // namespace voicecat::net
#endif // VOICECAT_HAS_NET

View File

@@ -4,9 +4,8 @@
* Design: docs/architecture.md (Net thread), docs/protocol.md §1 (framing).
* Implementation uses standalone Asio for sockets and timers.
*
* The real classes are compiled only when VOICECAT_HAS_NET is defined (dev/release/
* server-release — vcpkg deps on). The skeleton-preset stub definitions below keep the
* no-deps build green.
* Design: docs/architecture.md (Net thread), docs/protocol.md §1 (framing).
* Implementation uses standalone Asio for sockets and timers.
*/
#ifndef VOICECAT_NET_TRANSPORT_H
#define VOICECAT_NET_TRANSPORT_H
@@ -14,8 +13,6 @@
#include <cstdint>
#include <string>
#ifdef VOICECAT_HAS_NET
#define ASIO_STANDALONE 1
#include <asio.hpp>
@@ -226,21 +223,4 @@ class UdpMediaChannel {
} // namespace voicecat::net
#else // !VOICECAT_HAS_NET — skeleton stubs for the dev preset
namespace voicecat::net {
class TcpControlChannel {
public:
bool connected() const { return false; }
};
class UdpMediaChannel {
public:
bool bound() const { return false; }
};
} // namespace voicecat::net
#endif // VOICECAT_HAS_NET
#endif // VOICECAT_NET_TRANSPORT_H

View File

@@ -1,7 +1,5 @@
#include "protocol/envelope.h"
#ifdef VOICECAT_HAS_NET
#include "protocol/protocol.h"
#include <atomic>
@@ -30,5 +28,3 @@ uint64_t next_request_id() {
}
} // namespace voicecat::protocol
#endif // VOICECAT_HAS_NET

View File

@@ -5,13 +5,10 @@
* envelopes without touching proto types directly. All callers that do need
* the proto types can #include the generated header alongside this one.
*
* Requires VOICECAT_HAS_NET (protobuf codegen).
*/
#ifndef VOICECAT_PROTOCOL_ENVELOPE_H
#define VOICECAT_PROTOCOL_ENVELOPE_H
#ifdef VOICECAT_HAS_NET
#include <cstddef>
#include <cstdint>
#include <vector>
@@ -35,5 +32,4 @@ uint64_t next_request_id();
} // namespace voicecat::protocol
#endif // VOICECAT_HAS_NET
#endif // VOICECAT_PROTOCOL_ENVELOPE_H

View File

@@ -23,8 +23,6 @@ std::pair<const User*, const Stream*> SessionModel::find_user_by_ssrc(uint32_t s
return {nullptr, nullptr};
}
#ifdef VOICECAT_HAS_NET
namespace {
void copy_channel_audio(Channel& ch, const voicecat::v1::AudioConfig& a) {
@@ -160,6 +158,4 @@ void SessionModel::apply_channel_event(const voicecat::v1::ChannelEvent& ev) {
}
}
#endif // VOICECAT_HAS_NET
} // namespace voicecat::session

View File

@@ -12,9 +12,7 @@
#include <utility>
#include <vector>
#ifdef VOICECAT_HAS_NET
#include "proto/voicecat.pb.h"
#endif
namespace voicecat::session {
@@ -88,11 +86,9 @@ class SessionModel {
// Returns {nullptr, nullptr} if not found.
std::pair<const User*, const Stream*> find_user_by_ssrc(uint32_t ssrc) const;
#ifdef VOICECAT_HAS_NET
void apply_snapshot(const voicecat::v1::ServerStateSnapshot& snap);
void apply_user_event(const voicecat::v1::UserEvent& ev);
void apply_channel_event(const voicecat::v1::ChannelEvent& ev);
#endif
private:
std::vector<Channel> channels_;