20 Commits

Author SHA1 Message Date
3bc767f41b 完善AudioEngine <-> VstHost 流程 2025-10-11 16:29:17 +08:00
daiqingshuang
3a84c1dc10 重构音频环形缓冲区,增强文档注释,优化内存管理和扩容逻辑 2025-09-11 18:30:07 +08:00
daiqingshuang
24675ba237 重构音频环形缓冲区,简化构造函数,支持动态扩容和共享内存管理 2025-09-03 18:30:59 +08:00
daiqingshuang
c3f1664156 重构音频环形缓冲区,优化成员变量初始化,简化函数返回类型 2025-09-03 16:36:37 +08:00
daiqingshuang
31b9f459ba 添加音频环形缓冲区实现,支持共享内存管理和音频块处理 2025-09-03 16:27:39 +08:00
daiqingshuang
0bf1673c31 添加 .gitignore 文件,忽略 proto 目录 2025-09-02 17:30:12 +08:00
daiqingshuang
38095e67de 重构插件心跳管理,添加插件超时检测功能,优化 IPC 逻辑 2025-09-02 17:29:58 +08:00
daiqingshuang
3b236f7a39 添加项目同步服务的 Protobuf 定义,支持 MIDI 和自动化剪辑数据结构 2025-09-02 14:43:41 +08:00
daiqingshuang
b7edc41c7a 重构引擎心跳管理,使用 boost::atomic<heartbeat_t> 替代 boost::atomic_bool,增强进程监控功能 2025-09-01 15:53:37 +08:00
0a65fff8bd 修复退出时崩溃 2025-09-01 11:53:49 +08:00
daiqingshuang
ec29f5aaf6 添加插件远程调用节点和超时支持,增强 IPC 队列管理功能 2025-08-29 17:18:26 +08:00
a03ba378bc 添加 IPC 队列名称管理功能,重构创建和打开队列的逻辑以支持动态名称 2025-08-27 23:44:41 +08:00
daiqingshuang
7dad847277 重构共享内存管理,修复跨进程共享内存访问 2025-08-27 18:34:51 +08:00
daiqingshuang
a6d5b3fe32 重构共享内存字符串管理,添加 shm_string 类以支持 RPC 浅拷贝,更新 rpc_manager 以注册析构器 2025-08-27 11:46:30 +08:00
daiqingshuang
a77ef342cb 添加 shm_manager 类的字符串管理功能,支持创建、查找和销毁共享内存中的字符串对象 2025-08-26 18:01:18 +08:00
daiqingshuang
0219717936 添加 IPC 节点实现,支持共享内存队列的创建、打开和关闭,增强 RPC 消息处理功能 2025-08-26 17:42:55 +08:00
daiqingshuang
5266d6429d 添加共享内存管理和音频缓冲区功能,更新 CMake 配置以支持 Boost 组件 2025-08-26 17:19:56 +08:00
daiqingshuang
b96404e317 todo rpc框架 2025-08-19 19:13:40 +08:00
16235e0936 添加 RTEnvelope 结构及其 gRPC 相关文件,更新 CMake 配置以支持 Boost 和 spdlog 2025-08-18 14:36:38 +08:00
daiqingshuang
bf8ad281db TODO 2025-08-16 15:13:38 +08:00
65 changed files with 5263 additions and 14992 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ cmake-build-*/
/build
/.vs
*.DotSettings.user
.aider*

1
src/backend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/proto

View File

@@ -26,6 +26,9 @@ include(cmake_script/detect_os.cmake)
include(cmake_script/plugin_host_register.cmake)
include(cmake_script/configure_glfw_native.cmake)
include(CTest)
enable_testing()
# 使用自定义函数来配置项目的编译选项。
# @param STANDARD: 指定C++语言标准,此处为 C++23。
# @param INTERFACE_TARGET: 创建一个名为 config_target 的 INTERFACE 目标,
@@ -35,6 +38,8 @@ setup_project_options(
INTERFACE_TARGET config_target
)
add_definitions(-D_WIN32_WINNT=0x0A00 -DWIN32_LEAN_AND_MEAN -DNOMINMAX)
# NOTE: 硬编码构建目录可能会降低灵活性。
# 通常,更推荐的做法是让用户在调用 CMake 时通过 -B <build_dir> 参数来指定构建目录,
# 以支持灵活的 out-of-source builds。
@@ -49,6 +54,8 @@ configure_project_defaults()
find_package(gRPC CONFIG REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
find_package(ZeroMQ CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(Boost CONFIG REQUIRED COMPONENTS system interprocess lockfree asio process uuid circular_buffer thread)
# --- Protocol Buffers 编译 (Protocol Buffers Compilation) ---
# 使用自定义函数编译 .proto 文件,自动生成 C++ 源代码和 gRPC 服务代码。
@@ -58,7 +65,7 @@ find_package(ZeroMQ CONFIG REQUIRED)
# @param OUTPUT_PATH: 生成的 .pb.h, .pb.cc, .grpc.pb.h, .grpc.pb.cc 文件的输出目录。
# @param GRPC_ENABLED: 设置为 TRUE表示同时生成 gRPC 服务相关的代码。
compile_proto_files(
TARGET_NAME alicho_proto
TARGET_NAME AlichoProto
PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../proto
OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/proto
GRPC_ENABLED TRUE
@@ -68,3 +75,4 @@ add_subdirectory(src/misc)
add_subdirectory(src/vst2_host)
add_subdirectory(src/vst3_host)
add_subdirectory(src/engine)
add_subdirectory(tests)

View File

@@ -23,6 +23,11 @@ function(add_os_definitions target)
set(alicho_def_arch_64bit 0)
set(alicho_def_arch_32bit 0)
set(alicho_def_apple 0) # 用于 iOS 和 macOS 的通用定义
set(alicho_def_debug 0) # 当前是否处于调试模式
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(alicho_def_debug 1)
endif ()
# -- 操作系统检测与赋值 --
# 注意检测顺序:优先检测更具体的平台
@@ -113,6 +118,8 @@ function(add_os_definitions target)
list(APPEND definitions_list "ALICHO_PLATFORM_POSIX=${alicho_def_posix}")
list(APPEND definitions_list "ALICHO_PLATFORM_IS_MOBILE=${alicho_def_mobile}")
list(APPEND definitions_list "ALICHO_DEBUG=${alicho_def_debug}") # 当前是否处于调试模式
# --- 阶段 3: 应用所有定义 ---
# **关键:使用一次调用将所有定义添加到目标**
if(definitions_list) # 确保列表非空

View File

@@ -16,7 +16,7 @@ function(is_current_platform platform is_match)
if(UNIX AND NOT APPLE)
set(matches TRUE)
endif()
elseif(platform STREQUAL "mac")
elseif(platform STREQUAL "macos")
if(APPLE AND NOT IOS)
set(matches TRUE)
endif()

View File

@@ -1,649 +0,0 @@
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: daw_api.proto
#include "daw_api.pb.h"
#include "daw_api.grpc.pb.h"
#include <functional>
#include <grpcpp/support/async_stream.h>
#include <grpcpp/support/async_unary_call.h>
#include <grpcpp/impl/channel_interface.h>
#include <grpcpp/impl/client_unary_call.h>
#include <grpcpp/support/client_callback.h>
#include <grpcpp/support/message_allocator.h>
#include <grpcpp/support/method_handler.h>
#include <grpcpp/impl/rpc_service_method.h>
#include <grpcpp/support/server_callback.h>
#include <grpcpp/impl/server_callback_handlers.h>
#include <grpcpp/server_context.h>
#include <grpcpp/impl/service_type.h>
#include <grpcpp/support/sync_stream.h>
namespace daw {
namespace api {
static const char* TransportService_method_names[] = {
"/daw.api.TransportService/Play",
"/daw.api.TransportService/Pause",
"/daw.api.TransportService/Stop",
"/daw.api.TransportService/SetTempo",
};
std::unique_ptr< TransportService::Stub> TransportService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< TransportService::Stub> stub(new TransportService::Stub(channel, options));
return stub;
}
TransportService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
: channel_(channel), rpcmethod_Play_(TransportService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_Pause_(TransportService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_Stop_(TransportService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_SetTempo_(TransportService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status TransportService::Stub::Play(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Play_, context, request, response);
}
void TransportService::Stub::async::Play(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Play_, context, request, response, std::move(f));
}
void TransportService::Stub::async::Play(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Play_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncPlayRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Play_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncPlayRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncPlayRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TransportService::Stub::Pause(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Pause_, context, request, response);
}
void TransportService::Stub::async::Pause(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Pause_, context, request, response, std::move(f));
}
void TransportService::Stub::async::Pause(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Pause_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncPauseRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Pause_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncPauseRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncPauseRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TransportService::Stub::Stop(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Stop_, context, request, response);
}
void TransportService::Stub::async::Stop(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Stop_, context, request, response, std::move(f));
}
void TransportService::Stub::async::Stop(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Stop_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncStopRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Stop_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncStopRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncStopRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TransportService::Stub::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTempo_, context, request, response);
}
void TransportService::Stub::async::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTempo_, context, request, response, std::move(f));
}
void TransportService::Stub::async::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTempo_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncSetTempoRaw(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTempoRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTempo_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncSetTempoRaw(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSetTempoRaw(context, request, cq);
result->StartCall();
return result;
}
TransportService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
TransportService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TransportService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::Empty* req,
::daw::api::StatusResponse* resp) {
return service->Play(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TransportService_method_names[1],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TransportService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::Empty* req,
::daw::api::StatusResponse* resp) {
return service->Pause(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TransportService_method_names[2],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TransportService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::Empty* req,
::daw::api::StatusResponse* resp) {
return service->Stop(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TransportService_method_names[3],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TransportService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::SetTempoRequest* req,
::daw::api::StatusResponse* resp) {
return service->SetTempo(ctx, req, resp);
}, this)));
}
TransportService::Service::~Service() {
}
::grpc::Status TransportService::Service::Play(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TransportService::Service::Pause(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TransportService::Service::Stop(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TransportService::Service::SetTempo(::grpc::ServerContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
static const char* ProjectService_method_names[] = {
"/daw.api.ProjectService/NewProject",
"/daw.api.ProjectService/LoadProject",
"/daw.api.ProjectService/SaveProject",
};
std::unique_ptr< ProjectService::Stub> ProjectService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< ProjectService::Stub> stub(new ProjectService::Stub(channel, options));
return stub;
}
ProjectService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
: channel_(channel), rpcmethod_NewProject_(ProjectService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_LoadProject_(ProjectService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_SaveProject_(ProjectService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status ProjectService::Stub::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::ProjectState* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_NewProject_, context, request, response);
}
void ProjectService::Stub::async::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_NewProject_, context, request, response, std::move(f));
}
void ProjectService::Stub::async::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_NewProject_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::PrepareAsyncNewProjectRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::ProjectState, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_NewProject_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::AsyncNewProjectRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncNewProjectRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status ProjectService::Stub::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::daw::api::ProjectState* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_LoadProject_, context, request, response);
}
void ProjectService::Stub::async::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadProject_, context, request, response, std::move(f));
}
void ProjectService::Stub::async::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadProject_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::PrepareAsyncLoadProjectRaw(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::ProjectState, ::daw::api::LoadProjectRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_LoadProject_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::AsyncLoadProjectRaw(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncLoadProjectRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status ProjectService::Stub::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SaveProject_, context, request, response);
}
void ProjectService::Stub::async::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SaveProject_, context, request, response, std::move(f));
}
void ProjectService::Stub::async::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SaveProject_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* ProjectService::Stub::PrepareAsyncSaveProjectRaw(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SaveProjectRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SaveProject_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* ProjectService::Stub::AsyncSaveProjectRaw(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSaveProjectRaw(context, request, cq);
result->StartCall();
return result;
}
ProjectService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
ProjectService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ProjectService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::Empty* req,
::daw::api::ProjectState* resp) {
return service->NewProject(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ProjectService_method_names[1],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ProjectService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::LoadProjectRequest* req,
::daw::api::ProjectState* resp) {
return service->LoadProject(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ProjectService_method_names[2],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ProjectService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::SaveProjectRequest* req,
::daw::api::StatusResponse* resp) {
return service->SaveProject(ctx, req, resp);
}, this)));
}
ProjectService::Service::~Service() {
}
::grpc::Status ProjectService::Service::NewProject(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ProjectService::Service::LoadProject(::grpc::ServerContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ProjectService::Service::SaveProject(::grpc::ServerContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
static const char* TrackService_method_names[] = {
"/daw.api.TrackService/AddTrack",
"/daw.api.TrackService/RemoveTrack",
"/daw.api.TrackService/SetTrackVolume",
"/daw.api.TrackService/SetTrackPan",
};
std::unique_ptr< TrackService::Stub> TrackService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< TrackService::Stub> stub(new TrackService::Stub(channel, options));
return stub;
}
TrackService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
: channel_(channel), rpcmethod_AddTrack_(TrackService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_RemoveTrack_(TrackService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_SetTrackVolume_(TrackService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_SetTrackPan_(TrackService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status TrackService::Stub::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::daw::api::TrackInfo* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_AddTrack_, context, request, response);
}
void TrackService::Stub::async::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_AddTrack_, context, request, response, std::move(f));
}
void TrackService::Stub::async::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_AddTrack_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::TrackInfo>* TrackService::Stub::PrepareAsyncAddTrackRaw(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::TrackInfo, ::daw::api::AddTrackRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_AddTrack_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::TrackInfo>* TrackService::Stub::AsyncAddTrackRaw(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncAddTrackRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TrackService::Stub::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_RemoveTrack_, context, request, response);
}
void TrackService::Stub::async::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RemoveTrack_, context, request, response, std::move(f));
}
void TrackService::Stub::async::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RemoveTrack_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncRemoveTrackRaw(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::TrackIdRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_RemoveTrack_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncRemoveTrackRaw(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncRemoveTrackRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TrackService::Stub::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTrackVolume_, context, request, response);
}
void TrackService::Stub::async::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackVolume_, context, request, response, std::move(f));
}
void TrackService::Stub::async::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackVolume_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncSetTrackVolumeRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTrackVolumeRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTrackVolume_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncSetTrackVolumeRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSetTrackVolumeRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status TrackService::Stub::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTrackPan_, context, request, response);
}
void TrackService::Stub::async::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackPan_, context, request, response, std::move(f));
}
void TrackService::Stub::async::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackPan_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncSetTrackPanRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTrackPanRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTrackPan_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncSetTrackPanRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSetTrackPanRaw(context, request, cq);
result->StartCall();
return result;
}
TrackService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
TrackService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TrackService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::AddTrackRequest* req,
::daw::api::TrackInfo* resp) {
return service->AddTrack(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TrackService_method_names[1],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TrackService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::TrackIdRequest* req,
::daw::api::StatusResponse* resp) {
return service->RemoveTrack(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TrackService_method_names[2],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TrackService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::SetTrackVolumeRequest* req,
::daw::api::StatusResponse* resp) {
return service->SetTrackVolume(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
TrackService_method_names[3],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](TrackService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::SetTrackPanRequest* req,
::daw::api::StatusResponse* resp) {
return service->SetTrackPan(ctx, req, resp);
}, this)));
}
TrackService::Service::~Service() {
}
::grpc::Status TrackService::Service::AddTrack(::grpc::ServerContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TrackService::Service::RemoveTrack(::grpc::ServerContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TrackService::Service::SetTrackVolume(::grpc::ServerContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status TrackService::Service::SetTrackPan(::grpc::ServerContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
static const char* PluginService_method_names[] = {
"/daw.api.PluginService/LoadPlugin",
"/daw.api.PluginService/SetPluginParameter",
};
std::unique_ptr< PluginService::Stub> PluginService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< PluginService::Stub> stub(new PluginService::Stub(channel, options));
return stub;
}
PluginService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
: channel_(channel), rpcmethod_LoadPlugin_(PluginService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_SetPluginParameter_(PluginService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status PluginService::Stub::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::daw::api::PluginInfo* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_LoadPlugin_, context, request, response);
}
void PluginService::Stub::async::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadPlugin_, context, request, response, std::move(f));
}
void PluginService::Stub::async::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadPlugin_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::PluginInfo>* PluginService::Stub::PrepareAsyncLoadPluginRaw(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::PluginInfo, ::daw::api::LoadPluginRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_LoadPlugin_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::PluginInfo>* PluginService::Stub::AsyncLoadPluginRaw(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncLoadPluginRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::Status PluginService::Stub::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::daw::api::StatusResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetPluginParameter_, context, request, response);
}
void PluginService::Stub::async::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetPluginParameter_, context, request, response, std::move(f));
}
void PluginService::Stub::async::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetPluginParameter_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* PluginService::Stub::PrepareAsyncSetPluginParameterRaw(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetPluginParameterRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetPluginParameter_, context, request);
}
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* PluginService::Stub::AsyncSetPluginParameterRaw(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSetPluginParameterRaw(context, request, cq);
result->StartCall();
return result;
}
PluginService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
PluginService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< PluginService::Service, ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](PluginService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::LoadPluginRequest* req,
::daw::api::PluginInfo* resp) {
return service->LoadPlugin(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
PluginService_method_names[1],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< PluginService::Service, ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](PluginService::Service* service,
::grpc::ServerContext* ctx,
const ::daw::api::SetPluginParameterRequest* req,
::daw::api::StatusResponse* resp) {
return service->SetPluginParameter(ctx, req, resp);
}, this)));
}
PluginService::Service::~Service() {
}
::grpc::Status PluginService::Service::LoadPlugin(::grpc::ServerContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status PluginService::Service::SetPluginParameter(::grpc::ServerContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
} // namespace daw
} // namespace api

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
# @param ${PROJECT_NAME}: 目标名称。
# @param ${SRC_FILES}: 构成该目标的所有源文件。
add_executable(${PROJECT_NAME} ${SRC_FILES})
add_plugin_host_dependency(${PROJECT_NAME})
#add_plugin_host_dependency(${PROJECT_NAME})
# 将依赖库链接到我们的可执行文件目标。
# @param ${PROJECT_NAME}: 要链接的目标。
@@ -30,5 +30,9 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
gRPC::grpc++
protobuf::libprotobuf
libzmq
alicho_proto
AlichoProto
AlichoMisc
ws2_32
Boost::process
)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

View File

@@ -0,0 +1 @@
#include "engine_grpc.h"

View File

@@ -0,0 +1,96 @@
#pragma once
#include "ctrl/frontend_to_engine.grpc.pb.h"
class daw_api_project_service : public daw::api::ProjectService::Service {
public:
grpc::Status NewProject(grpc::ServerContext* context,
const google::protobuf::Empty* request,
daw::api::ProjectState* response) override {
return grpc::Status::OK;
}
grpc::Status LoadProject(grpc::ServerContext* context,
const daw::api::LoadProjectRequest* request,
daw::api::ProjectState* response) override {
return grpc::Status::OK;
}
grpc::Status SaveProject(grpc::ServerContext* context,
const daw::api::SaveProjectRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};
class daw_api_transport_service : public daw::api::TransportService::Service {
public:
virtual ~daw_api_transport_service() override;
virtual grpc::Status Play(grpc::ServerContext* context,
const google::protobuf::Empty* request,
daw::api::StatusResponse* response) override;
virtual grpc::Status Pause(grpc::ServerContext* context,
const google::protobuf::Empty* request,
daw::api::StatusResponse* response) override;
virtual grpc::Status Stop(grpc::ServerContext* context,
const google::protobuf::Empty* request,
daw::api::StatusResponse* response) override;
virtual grpc::Status SetTempo(grpc::ServerContext* context,
const daw::api::SetTempoRequest* request,
daw::api::StatusResponse* response) override;
};
class daw_api_track_service : public daw::api::TrackService::Service {
public:
grpc::Status AddTrack(grpc::ServerContext* context,
const daw::api::AddTrackRequest* request,
daw::api::TrackInfo* response) override {
return grpc::Status::OK;
}
grpc::Status RemoveTrack(grpc::ServerContext* context,
const daw::api::TrackIdRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
grpc::Status SetTrackVolume(grpc::ServerContext* context,
const daw::api::SetTrackVolumeRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
grpc::Status SetTrackPan(grpc::ServerContext* context,
const daw::api::SetTrackPanRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};
class daw_api_plugin_service : public daw::api::PluginService::Service {
public:
grpc::Status LoadPlugin(grpc::ServerContext* context,
const daw::api::LoadPluginRequest* request,
daw::api::PluginInfo* response) override {
return grpc::Status::OK;
}
grpc::Status SetPluginParameter(grpc::ServerContext* context,
const daw::api::SetPluginParameterRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};

View File

@@ -0,0 +1 @@
#include "plugin_host.h"

View File

@@ -0,0 +1,34 @@
#pragma once
#include <span>
#include "midi_type.h"
class plugin_host {
public:
virtual void process(std::span<float*> in_buffers, std::span<float*> out_buffers, int32_t in_frames) = 0;
virtual void process(std::span<double*> in_buffers, std::span<double*> out_buffers, int32_t in_frames) = 0;
virtual auto send_events(std::span<midi_type::midi_event> ev) -> bool = 0;
[[nodiscard]] virtual auto get_name() -> std::string = 0;
[[nodiscard]] virtual auto get_vendor() -> std::string = 0;
[[nodiscard]] virtual auto get_version() -> std::string = 0;
[[nodiscard]] virtual auto get_product() -> std::string = 0;
[[nodiscard]] virtual auto get_input_count() const noexcept -> int = 0;
[[nodiscard]] virtual auto get_output_count() const noexcept -> int = 0;
[[nodiscard]] virtual auto get_parameter_count() const noexcept -> int = 0;
[[nodiscard]] virtual auto can_double() const noexcept -> bool = 0;
[[nodiscard]] virtual auto is_synth() const noexcept -> bool = 0;
[[nodiscard]] virtual auto get_unique_id() const noexcept -> int = 0;
[[nodiscard]] virtual auto is_valid() const noexcept -> bool = 0;
[[nodiscard]] virtual auto get_parameter(int idx) -> float = 0;
virtual void set_parameter(int idx, float v) = 0;
virtual void set_sample_rate(float in_sample_rate) = 0;
virtual void set_block_size(int32_t in_block_size) = 0;
[[nodiscard]] virtual auto get_program_name() -> std::string = 0;
virtual void set_program_name(const std::string& name) = 0;
virtual void set_program(int idx) = 0;
[[nodiscard]] virtual auto get_program() -> int32_t = 0;
};

View File

@@ -0,0 +1,351 @@
//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4 $Date: 2006/06/20 17:22:55 $
//
// Category : VST 2.x Interfaces
// Filename : aeffect.h
// Created by : Steinberg Media Technologies
// Description : Definition of AEffect structure
//
// © 2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------
#ifndef __aeffect__
#define __aeffect__
// gcc based compiler, or CodeWarrior on Mac OS X
#if ((defined(__GNUC__) && (defined(__APPLE_CPP__) || defined(__APPLE_CC__))) || (defined (__MWERKS__) && defined (__MACH__)))
#ifndef TARGET_API_MAC_CARBON
#define TARGET_API_MAC_CARBON 1
#endif
#if __ppc__
#ifndef VST_FORCE_DEPRECATED
#define VST_FORCE_DEPRECATED 0
#endif
#endif
#endif
#if TARGET_API_MAC_CARBON
#ifdef __LP64__
#pragma options align=power
#else
#pragma options align=mac68k
#endif
#define VSTCALLBACK
#elif defined __BORLANDC__
#pragma -a8
#elif defined(__GNUC__)
#pragma pack(push,8)
#define VSTCALLBACK __cdecl
#elif defined(WIN32) || defined(__FLAT__) || defined CBUILDER
#pragma pack(push)
#pragma pack(8)
#define VSTCALLBACK __cdecl
#else
#define VSTCALLBACK
#endif
//-------------------------------------------------------------------------------------------------------
#include <string.h> // for strncpy
//-------------------------------------------------------------------------------------------------------
// VST Version
//-------------------------------------------------------------------------------------------------------
/** Define SDK Version (you can generate different versions (from 2.0 to 2.4) of this SDK by setting the unwanted extensions to 0). */
#define VST_2_1_EXTENSIONS 1 ///< Version 2.1 extensions (08-06-2000)
#define VST_2_2_EXTENSIONS 1 ///< Version 2.2 extensions (08-06-2001)
#define VST_2_3_EXTENSIONS 1 ///< Version 2.3 extensions (20-05-2003)
#ifndef VST_2_4_EXTENSIONS
#define VST_2_4_EXTENSIONS 1 ///< Version 2.4 extensions (01-01-2006)
#endif
/** Current VST Version */
#if VST_2_4_EXTENSIONS
#define kVstVersion 2400
#elif VST_2_3_EXTENSIONS
#define kVstVersion 2300
#elif VST_2_2_EXTENSIONS
#define kVstVersion 2200
#elif VST_2_1_EXTENSIONS
#define kVstVersion 2100
#else
#define kVstVersion 2
#endif
/** Disable for Hosts to serve Plug-ins below VST 2.4 */
#ifndef VST_FORCE_DEPRECATED
#define VST_FORCE_DEPRECATED VST_2_4_EXTENSIONS
#endif
/** Declares identifier as deprecated. */
#if VST_FORCE_DEPRECATED
#define DECLARE_VST_DEPRECATED(identifier) __##identifier##Deprecated
#else
#define DECLARE_VST_DEPRECATED(identifier) identifier
#endif
/** Define for 64 Bit Platform. */
#ifndef VST_64BIT_PLATFORM
#define VST_64BIT_PLATFORM _WIN64 || __LP64__
#endif
//-------------------------------------------------------------------------------------------------------
// Integral Types
//-------------------------------------------------------------------------------------------------------
#ifdef WIN32
typedef short VstInt16; ///< 16 bit integer type
typedef int VstInt32; ///< 32 bit integer type
typedef __int64 VstInt64; ///< 64 bit integer type
#else
#include <stdint.h>
typedef int16_t VstInt16; ///< 16 bit integer type
typedef int32_t VstInt32; ///< 32 bit integer type
typedef int64_t VstInt64; ///< 64 bit integer type
#endif
//-------------------------------------------------------------------------------------------------------
// Generic Types
//-------------------------------------------------------------------------------------------------------
#if VST_64BIT_PLATFORM
typedef VstInt64 VstIntPtr; ///< platform-dependent integer type, same size as pointer
#else
typedef VstInt32 VstIntPtr; ///< platform-dependent integer type, same size as pointer
#endif
//-------------------------------------------------------------------------------------------------------
// Misc. Definition
//-------------------------------------------------------------------------------------------------------
#undef CCONST
struct AEffect;
/// @cond ignore
typedef VstIntPtr (VSTCALLBACK *audioMasterCallback) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt);
typedef VstIntPtr (VSTCALLBACK *AEffectDispatcherProc) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt);
typedef void (VSTCALLBACK *AEffectProcessProc) (AEffect* effect, float** inputs, float** outputs, VstInt32 sampleFrames);
typedef void (VSTCALLBACK *AEffectProcessDoubleProc) (AEffect* effect, double** inputs, double** outputs, VstInt32 sampleFrames);
typedef void (VSTCALLBACK *AEffectSetParameterProc) (AEffect* effect, VstInt32 index, float parameter);
typedef float (VSTCALLBACK *AEffectGetParameterProc) (AEffect* effect, VstInt32 index);
/// @endcond
/** Four Character Constant (for AEffect->uniqueID) */
#define CCONST(a, b, c, d) \
((((VstInt32)a) << 24) | (((VstInt32)b) << 16) | (((VstInt32)c) << 8) | (((VstInt32)d) << 0))
/** AEffect magic number */
#define kEffectMagic CCONST ('V', 's', 't', 'P')
//-------------------------------------------------------------------------------------------------------
/** Basic VST Effect "C" Interface. */
//-------------------------------------------------------------------------------------------------------
struct AEffect
{
//-------------------------------------------------------------------------------------------------------
VstInt32 magic; ///< must be #kEffectMagic ('VstP')
/** Host to Plug-in dispatcher @see AudioEffect::dispatcher */
AEffectDispatcherProc dispatcher;
/** \deprecated Accumulating process mode is deprecated in VST 2.4! Use AEffect::processReplacing instead! */
AEffectProcessProc DECLARE_VST_DEPRECATED (process);
/** Set new value of automatable parameter @see AudioEffect::setParameter */
AEffectSetParameterProc setParameter;
/** Returns current value of automatable parameter @see AudioEffect::getParameter*/
AEffectGetParameterProc getParameter;
VstInt32 numPrograms; ///< number of programs
VstInt32 numParams; ///< all programs are assumed to have numParams parameters
VstInt32 numInputs; ///< number of audio inputs
VstInt32 numOutputs; ///< number of audio outputs
VstInt32 flags; ///< @see VstAEffectFlags
VstIntPtr resvd1; ///< reserved for Host, must be 0
VstIntPtr resvd2; ///< reserved for Host, must be 0
VstInt32 initialDelay; ///< for algorithms which need input in the first place (Group delay or latency in Samples). This value should be initialized in a resume state.
VstInt32 DECLARE_VST_DEPRECATED (realQualities); ///< \deprecated unused member
VstInt32 DECLARE_VST_DEPRECATED (offQualities); ///< \deprecated unused member
float DECLARE_VST_DEPRECATED (ioRatio); ///< \deprecated unused member
void* object; ///< #AudioEffect class pointer
void* user; ///< user-defined pointer
VstInt32 uniqueID; ///< registered unique identifier (register it at Steinberg 3rd party support Web). This is used to identify a plug-in during save+load of preset and project.
VstInt32 version; ///< plug-in version (example 1100 for version 1.1.0.0)
/** Process audio samples in replacing mode @see AudioEffect::processReplacing */
AEffectProcessProc processReplacing;
#if VST_2_4_EXTENSIONS
/** Process double-precision audio samples in replacing mode @see AudioEffect::processDoubleReplacing */
AEffectProcessDoubleProc processDoubleReplacing;
char future[56]; ///< reserved for future use (please zero)
#else
char future[60]; ///< reserved for future use (please zero)
#endif
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
/** AEffect flags */
//-------------------------------------------------------------------------------------------------------
enum VstAEffectFlags
{
//-------------------------------------------------------------------------------------------------------
effFlagsHasEditor = 1 << 0, ///< set if the plug-in provides a custom editor
effFlagsCanReplacing = 1 << 4, ///< supports replacing process mode (which should the default mode in VST 2.4)
effFlagsProgramChunks = 1 << 5, ///< program data is handled in formatless chunks
effFlagsIsSynth = 1 << 8, ///< plug-in is a synth (VSTi), Host may assign mixer channels for its outputs
effFlagsNoSoundInStop = 1 << 9, ///< plug-in does not produce sound when input is all silence
#if VST_2_4_EXTENSIONS
effFlagsCanDoubleReplacing = 1 << 12, ///< plug-in supports double precision processing
#endif
DECLARE_VST_DEPRECATED (effFlagsHasClip) = 1 << 1, ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effFlagsHasVu) = 1 << 2, ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effFlagsCanMono) = 1 << 3, ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effFlagsExtIsAsync) = 1 << 10, ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effFlagsExtHasBuffer) = 1 << 11 ///< \deprecated deprecated in VST 2.4
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
/** Basic dispatcher Opcodes (Host to Plug-in) */
//-------------------------------------------------------------------------------------------------------
enum AEffectOpcodes
{
effOpen = 0, ///< no arguments @see AudioEffect::open
effClose, ///< no arguments @see AudioEffect::close
effSetProgram, ///< [value]: new program number @see AudioEffect::setProgram
effGetProgram, ///< [return value]: current program number @see AudioEffect::getProgram
effSetProgramName, ///< [ptr]: char* with new program name, limited to #kVstMaxProgNameLen @see AudioEffect::setProgramName
effGetProgramName, ///< [ptr]: char buffer for current program name, limited to #kVstMaxProgNameLen @see AudioEffect::getProgramName
effGetParamLabel, ///< [ptr]: char buffer for parameter label, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterLabel
effGetParamDisplay, ///< [ptr]: char buffer for parameter display, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterDisplay
effGetParamName, ///< [ptr]: char buffer for parameter name, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterName
DECLARE_VST_DEPRECATED (effGetVu), ///< \deprecated deprecated in VST 2.4
effSetSampleRate, ///< [opt]: new sample rate for audio processing @see AudioEffect::setSampleRate
effSetBlockSize, ///< [value]: new maximum block size for audio processing @see AudioEffect::setBlockSize
effMainsChanged, ///< [value]: 0 means "turn off", 1 means "turn on" @see AudioEffect::suspend @see AudioEffect::resume
effEditGetRect, ///< [ptr]: #ERect** receiving pointer to editor size @see ERect @see AEffEditor::getRect
effEditOpen, ///< [ptr]: system dependent Window pointer, e.g. HWND on Windows @see AEffEditor::open
effEditClose, ///< no arguments @see AEffEditor::close
DECLARE_VST_DEPRECATED (effEditDraw), ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effEditMouse), ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effEditKey), ///< \deprecated deprecated in VST 2.4
effEditIdle, ///< no arguments @see AEffEditor::idle
DECLARE_VST_DEPRECATED (effEditTop), ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effEditSleep), ///< \deprecated deprecated in VST 2.4
DECLARE_VST_DEPRECATED (effIdentify), ///< \deprecated deprecated in VST 2.4
effGetChunk, ///< [ptr]: void** for chunk data address [index]: 0 for bank, 1 for program @see AudioEffect::getChunk
effSetChunk, ///< [ptr]: chunk data [value]: byte size [index]: 0 for bank, 1 for program @see AudioEffect::setChunk
effNumOpcodes
};
//-------------------------------------------------------------------------------------------------------
/** Basic dispatcher Opcodes (Plug-in to Host) */
//-------------------------------------------------------------------------------------------------------
enum AudioMasterOpcodes
{
//-------------------------------------------------------------------------------------------------------
audioMasterAutomate = 0, ///< [index]: parameter index [opt]: parameter value @see AudioEffect::setParameterAutomated
audioMasterVersion, ///< [return value]: Host VST version (for example 2400 for VST 2.4) @see AudioEffect::getMasterVersion
audioMasterCurrentId, ///< [return value]: current unique identifier on shell plug-in @see AudioEffect::getCurrentUniqueId
audioMasterIdle, ///< no arguments @see AudioEffect::masterIdle
DECLARE_VST_DEPRECATED (audioMasterPinConnected) ///< \deprecated deprecated in VST 2.4 r2
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
/** String length limits (in characters excl. 0 byte) */
//-------------------------------------------------------------------------------------------------------
enum VstStringConstants
{
//-------------------------------------------------------------------------------------------------------
kVstMaxProgNameLen = 24, ///< used for #effGetProgramName, #effSetProgramName, #effGetProgramNameIndexed
kVstMaxParamStrLen = 8, ///< used for #effGetParamLabel, #effGetParamDisplay, #effGetParamName
kVstMaxVendorStrLen = 64, ///< used for #effGetVendorString, #audioMasterGetVendorString
kVstMaxProductStrLen = 64, ///< used for #effGetProductString, #audioMasterGetProductString
kVstMaxEffectNameLen = 32 ///< used for #effGetEffectName
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
/** String copy taking care of null terminator. */
//-------------------------------------------------------------------------------------------------------
inline char* vst_strncpy (char* dst, const char* src, size_t maxLen)
{
char* result = strncpy (dst, src, maxLen);
dst[maxLen] = 0;
return result;
}
//-------------------------------------------------------------------------------------------------------
/** String concatenation taking care of null terminator. */
//-------------------------------------------------------------------------------------------------------
inline char* vst_strncat (char* dst, const char* src, size_t maxLen)
{
char* result = strncat (dst, src, maxLen);
dst[maxLen] = 0;
return result;
}
//-------------------------------------------------------------------------------------------------------
/** Cast #VstIntPtr to pointer. */
//-------------------------------------------------------------------------------------------------------
template <class T> inline T* FromVstPtr (VstIntPtr& arg)
{
T** address = (T**)&arg;
return *address;
}
//-------------------------------------------------------------------------------------------------------
/** Cast pointer to #VstIntPtr. */
//-------------------------------------------------------------------------------------------------------
template <class T> inline VstIntPtr ToVstPtr (T* ptr)
{
VstIntPtr* address = (VstIntPtr*)&ptr;
return *address;
}
//-------------------------------------------------------------------------------------------------------
/** Structure used for #effEditGetRect. */
//-------------------------------------------------------------------------------------------------------
struct ERect
{
//-------------------------------------------------------------------------------------------------------
VstInt16 top; ///< top coordinate
VstInt16 left; ///< left coordinate
VstInt16 bottom; ///< bottom coordinate
VstInt16 right; ///< right coordinate
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
#if TARGET_API_MAC_CARBON
#pragma options align=reset
#elif defined(WIN32) || defined(__FLAT__) || defined(__GNUC__)
#pragma pack(pop)
#elif defined __BORLANDC__
#pragma -a-
#endif
#endif // __aeffect__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4 $Date: 2006/02/09 11:05:51 $
//
// Category : VST 2.x Interfaces
// Filename : vstfxstore.h
// Created by : Steinberg Media Technologies
// Description : Definition of Program (fxp) and Bank (fxb) structures
//
// © 2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------
#ifndef __vstfxstore__
#define __vstfxstore__
#ifndef __aeffect__
#include "aeffect.h"
#endif
//-------------------------------------------------------------------------------------------------------
/** Root chunk identifier for Programs (fxp) and Banks (fxb). */
#define cMagic 'CcnK'
/** Regular Program (fxp) identifier. */
#define fMagic 'FxCk'
/** Regular Bank (fxb) identifier. */
#define bankMagic 'FxBk'
/** Program (fxp) identifier for opaque chunk data. */
#define chunkPresetMagic 'FPCh'
/** Bank (fxb) identifier for opaque chunk data. */
#define chunkBankMagic 'FBCh'
/*
Note: The C data structures below are for illustration only. You can not read/write them directly.
The byte order on disk of fxp and fxb files is Big Endian. You have to swap integer
and floating-point values on Little Endian platforms (Windows, MacIntel)!
*/
//-------------------------------------------------------------------------------------------------------
/** Program (fxp) structure. */
//-------------------------------------------------------------------------------------------------------
struct fxProgram
{
//-------------------------------------------------------------------------------------------------------
VstInt32 chunkMagic; ///< 'CcnK'
VstInt32 byteSize; ///< size of this chunk, excl. magic + byteSize
VstInt32 fxMagic; ///< 'FxCk' (regular) or 'FPCh' (opaque chunk)
VstInt32 version; ///< format version (currently 1)
VstInt32 fxID; ///< fx unique ID
VstInt32 fxVersion; ///< fx version
VstInt32 numParams; ///< number of parameters
char prgName[28]; ///< program name (null-terminated ASCII string)
union
{
float params[1]; ///< variable sized array with parameter values
struct
{
VstInt32 size; ///< size of program data
char chunk[1]; ///< variable sized array with opaque program data
} data; ///< program chunk data
} content; ///< program content depending on fxMagic
//-------------------------------------------------------------------------------------------------------
};
//-------------------------------------------------------------------------------------------------------
/** Bank (fxb) structure. */
//-------------------------------------------------------------------------------------------------------
struct fxBank
{
//-------------------------------------------------------------------------------------------------------
VstInt32 chunkMagic; ///< 'CcnK'
VstInt32 byteSize; ///< size of this chunk, excl. magic + byteSize
VstInt32 fxMagic; ///< 'FxBk' (regular) or 'FBCh' (opaque chunk)
VstInt32 version; ///< format version (1 or 2)
VstInt32 fxID; ///< fx unique ID
VstInt32 fxVersion; ///< fx version
VstInt32 numPrograms; ///< number of programs
#if VST_2_4_EXTENSIONS
VstInt32 currentProgram; ///< version 2: current program number
char future[124]; ///< reserved, should be zero
#else
char future[128]; ///< reserved, should be zero
#endif
union
{
fxProgram programs[1]; ///< variable number of programs
struct
{
VstInt32 size; ///< size of bank data
char chunk[1]; ///< variable sized array with opaque bank data
} data; ///< bank chunk data
} content; ///< bank content depending on fxMagic
//-------------------------------------------------------------------------------------------------------
};
#endif // __vstfxstore__

View File

@@ -0,0 +1 @@
#include "vst2_host.h"

View File

@@ -0,0 +1,5 @@
#pragma once
class vst2_host {
};

View File

@@ -1,114 +1,184 @@
//
// Created by A on 2025/8/12.
//
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include "zmq.h"
#include "daw_api.grpc.pb.h"
#include "ipc/ipc_node.h"
#include "ipc/shm_mgr.h"
#include "misc_type.h"
#include "plugin_manage/plugin_host_manager.h"
#include "rpc/engine_rpc.h"
class daw_api_project_service : public daw::api::ProjectService::Service {
boost::atomic<heartbeat_t>* engine_heartbeat = nullptr;
namespace {
constexpr float PI = 3.14159265359f;
}
class AudioEngine {
public:
grpc::Status NewProject(grpc::ServerContext* context,
const daw::api::Empty* request,
daw::api::ProjectState* response) override {
static constexpr size_t AUDIO_BLOCK_SIZE = 512;
static constexpr size_t SAMPLE_RATE = 44100;
static constexpr std::chrono::milliseconds PROCESSING_INTERVAL{10};
return grpc::Status::OK;
AudioEngine() : running_(false), processing_(false) {}
void start() {
running_ = true;
shm_mgr::get_instance().init(true);
(void)shm_mgr::msg_shm().destroy<boost::atomic<heartbeat_t>>("engine_heartbeat");
engine_heartbeat = shm_mgr::msg_shm().construct_with_name<boost::atomic<heartbeat_t>>(
"engine_heartbeat", std::chrono::high_resolution_clock::now().time_since_epoch().count());
heartbeat_thread_ = std::thread([this]() { heartbeat_loop(); });
audio_thread_ = std::thread([this]() { audio_processing_loop(); });
}
grpc::Status LoadProject(grpc::ServerContext* context,
const daw::api::LoadProjectRequest* request,
daw::api::ProjectState* response) override {
void stop() {
running_ = false;
cv_.notify_all();
return grpc::Status::OK;
if (heartbeat_thread_.joinable()) {
heartbeat_thread_.join();
}
if (audio_thread_.joinable()) {
audio_thread_.join();
}
if (engine_heartbeat) {
*engine_heartbeat = 0;
}
}
grpc::Status SaveProject(grpc::ServerContext* context,
const daw::api::SaveProjectRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
private:
void heartbeat_loop() {
while (running_) {
if (engine_heartbeat) {
*engine_heartbeat = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void audio_processing_loop() {
engine_ipc_node node;
auto& host_manager = plugin_host_manager::get_instance();
auto host_instance = host_manager.load_plugin(R"(D:\Projects\Alicho\src\backend\src\vst2_host\test\4Front Piano x64.dll)");
if (!host_instance) {
std::cerr << "Failed to load plugin" << std::endl;
return;
}
auto& audio_rb = host_instance->plugin_node_.get_audio_rb();
setup_audio_parameters(host_instance);
std::unique_lock<std::mutex> lock(mutex_);
while (running_) {
auto start_time = std::chrono::steady_clock::now();
node.process_rpc();
host_manager.tick();
if (!host_instance->is_process_running()) {
std::cerr << "Plugin process is no longer running." << std::endl;
break;
}
process_audio_blocks(audio_rb);
auto end_time = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
if (elapsed < PROCESSING_INTERVAL) {
auto sleep_time = PROCESSING_INTERVAL - elapsed;
cv_.wait_for(lock, sleep_time, [this] { return !running_; });
} else {
std::cerr << "Audio processing timeout: " << elapsed.count() << "ms" << std::endl;
}
}
}
void setup_audio_parameters(const std::shared_ptr<plugin_instance>& /*plugin*/) {
// TODO: propagate engine sample rate and block size to the plugin host via RPC.
}
void process_audio_blocks(audio_ring_buffer<float>& rb) {
const auto processed_block = rb.get_processed_block();
if (!processed_block.data) {
if (!processing_) {
const auto pending_block = rb.acquire_pending_block(AUDIO_BLOCK_SIZE);
if (pending_block.data) {
generate_audio_data(pending_block);
rb.commit_pending_block(pending_block);
}
}
processing_ = true;
} else {
handle_processed_audio(processed_block);
rb.release_processed_block();
processing_ = false;
}
}
void generate_audio_data(const audio_ring_buffer<float>::audio_block& block) {
static float phase = 0.0f;
const float frequency = 440.0f;
const float phase_increment = 2.0f * PI * frequency / static_cast<float>(SAMPLE_RATE);
for (size_t i = 0; i < block.size; ++i) {
block.data[i] = std::sin(phase) * 0.1f;
phase += phase_increment;
if (phase > 2.0f * PI) {
phase -= 2.0f * PI;
}
}
}
void handle_processed_audio(const audio_ring_buffer<float>::audio_block& block) {
float peak = 0.0f;
float rms = 0.0f;
for (size_t i = 0; i < block.size; ++i) {
const float sample = std::abs(block.data[i]);
peak = std::max(peak, sample);
rms += sample * sample;
}
rms = std::sqrt(rms / static_cast<float>(block.size));
static int counter = 0;
if (++counter % 100 == 0) {
std::cout << "Audio stats - Peak: " << peak << ", RMS: " << rms << std::endl;
}
}
std::thread heartbeat_thread_;
std::thread audio_thread_;
std::atomic<bool> running_;
std::atomic<bool> processing_;
std::mutex mutex_;
std::condition_variable cv_;
};
class daw_api_transport_service : public daw::api::TransportService::Service {
public:
grpc::Status Play(grpc::ServerContext* context,
const daw::api::Empty* request,
daw::api::StatusResponse* response) override {
int main(int /*argc*/, char* /*argv*/[]) {
AudioEngine engine;
return grpc::Status::OK;
}
engine.start();
grpc::Status Pause(grpc::ServerContext* context,
const daw::api::Empty* request,
daw::api::StatusResponse* response) override {
std::cout << "Audio engine started. Press Enter to stop..." << std::endl;
std::cin.get();
return grpc::Status::OK;
}
engine.stop();
grpc::Status Stop(grpc::ServerContext* context,
const daw::api::Empty* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
grpc::Status SetTempo(grpc::ServerContext* context,
const daw::api::SetTempoRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};
class daw_api_track_service : public daw::api::TrackService::Service {
public:
grpc::Status AddTrack(grpc::ServerContext* context,
const daw::api::AddTrackRequest* request,
daw::api::TrackInfo* response) override {
return grpc::Status::OK;
}
grpc::Status RemoveTrack(grpc::ServerContext* context,
const daw::api::TrackIdRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
grpc::Status SetTrackVolume(grpc::ServerContext* context,
const daw::api::SetTrackVolumeRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
grpc::Status SetTrackPan(grpc::ServerContext* context,
const daw::api::SetTrackPanRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};
class daw_api_plugin_service : public daw::api::PluginService::Service {
public:
grpc::Status LoadPlugin(grpc::ServerContext* context,
const daw::api::LoadPluginRequest* request,
daw::api::PluginInfo* response) override {
return grpc::Status::OK;
}
grpc::Status SetPluginParameter(grpc::ServerContext* context,
const daw::api::SetPluginParameterRequest* request,
daw::api::StatusResponse* response) override {
return grpc::Status::OK;
}
};
int main(int argc, char *argv[])
{
return 0;
}

View File

@@ -0,0 +1,103 @@
#include "plugin_host_manager.h"
#include <chrono>
#include <filesystem>
#include <thread>
#include <vector>
#include <spdlog/spdlog.h>
#include <boost/asio/steady_timer.hpp>
#include "plugin_instance.h"
plugin_host_manager::~plugin_host_manager() {
std::vector<uint32_t> ids_to_unload;
{
std::lock_guard lock(mutex_);
for (const auto& id: plugin_instances_ | std::views::keys) {
ids_to_unload.push_back(id);
}
}
// Request graceful shutdown for all plugins first.
for (const auto& id: ids_to_unload) {
if (auto instance = get_plugin_instance(id)) {
instance->request_graceful_shutdown();
}
}
// Give plugins a moment to exit cleanly.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Then force terminate any remaining processes.
for (const auto& id: ids_to_unload) {
unload_plugin(id);
}
}
std::shared_ptr<plugin_instance> plugin_host_manager::load_plugin(const std::filesystem::path& plugin_binary_path) {
auto plugin_id = get_next_plugin_id();
std::shared_ptr<plugin_instance> instance;
try {
instance = std::make_shared<plugin_instance>(io_context_, plugin_id, plugin_binary_path);
// Remove the instance from the manager once the host process exits.
instance->async_wait_for_exit([this, plugin_id](int exit_code) {
spdlog::info("Plugin {} exited with code {}", plugin_id, exit_code);
std::lock_guard lock(mutex_);
plugin_instances_.erase(plugin_id);
});
std::lock_guard lock(mutex_);
plugin_instances_.emplace(plugin_id, instance);
}
catch (const std::exception& e) {
spdlog::error("无法加载插件 {}{}", plugin_binary_path.string(), e.what());
}
return instance;
}
void plugin_host_manager::unload_plugin(uint32_t plugin_id) {
if (auto instance = get_plugin_instance(plugin_id)) {
// Try graceful shutdown first.
instance->request_graceful_shutdown();
// Allow a short grace period.
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// Force terminate if the process is still running.
if (instance->is_process_running()) {
instance->kill_process(true);
}
std::lock_guard lock(mutex_);
plugin_instances_.erase(plugin_id);
}
}
void plugin_host_manager::tick() {
// Service asynchronous operations.
io_context_.poll();
// Remove dead plugin instances.
std::lock_guard lock(mutex_);
for (auto it = plugin_instances_.begin(); it != plugin_instances_.end(); ) {
if (!it->second->is_process_running()) {
spdlog::info("Removing dead plugin instance {}", it->first);
it = plugin_instances_.erase(it);
} else {
++it;
}
}
}
std::shared_ptr<plugin_instance> plugin_host_manager::get_plugin_instance(uint32_t plugin_id) {
std::lock_guard lock(mutex_);
const auto it = plugin_instances_.find(plugin_id);
if (it != plugin_instances_.end()) {
return it->second;
}
return {};
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include <atomic>
#include <memory>
#include <ranges>
#include <unordered_map>
#include "lazy_singleton.h"
#include "plugin_instance.h"
class plugin_host_manager : public lazy_singleton<plugin_host_manager> {
public:
~plugin_host_manager();
std::shared_ptr<plugin_instance> load_plugin(const std::filesystem::path& plugin_binary_path);
void unload_plugin(uint32_t plugin_id);
void tick();
std::shared_ptr<plugin_instance> get_plugin_instance(uint32_t plugin_id);
template<typename Func>
void for_each_plugin_instance(Func&& func) {
std::lock_guard lock(mutex_);
for (const auto& instance: plugin_instances_ | std::views::values) {
func(instance.get());
}
}
private:
auto get_next_plugin_id() {
return plugin_id_.fetch_add(1);
}
std::mutex mutex_;
std::unordered_map<uint32_t, std::shared_ptr<plugin_instance>> plugin_instances_;
std::atomic_uint32_t plugin_id_ = 1;
boost::asio::io_context io_context_;
};

View File

@@ -0,0 +1,97 @@
#include "plugin_instance.h"
#include <filesystem>
#include <spdlog/spdlog.h>
namespace bi = boost::interprocess;
namespace bp = boost::process_v2;
[[nodiscard]] static auto get_plugin_id_name(uint32_t in_id) {
return "plugin_" + std::to_string(in_id);
}
auto execute_plugin_process(uint32_t id, boost::asio::io_context& ctx, const std::filesystem::path& plugin_path) {
if (!std::filesystem::exists(plugin_path)) {
throw std::runtime_error("插件路径不存在:" + plugin_path.string());
}
if (!plugin_path.has_extension()) {
throw std::runtime_error("插件路径必须带有扩展名:" + plugin_path.string());
}
// 启动沙箱进程
const auto& ext = plugin_path.extension();
const static std::unordered_map<std::string, std::string> supported_exts = {
{".so", "AlichoPluginHostVst2.exe"},
{".dll", "AlichoPluginHostVst2.exe"},
{".dylib", "AlichoPluginHostVst2.exe"},
{".vst2", "AlichoPluginHostVst2.exe"},
{".vst3", "AlichoPluginHostVst3.exe"},
{".clap", "AlichoPluginHostClap.exe"},
};
if (!supported_exts.contains(ext.string())) {
throw std::runtime_error("不支持的插件文件类型:" + ext.string());
}
const auto& host_executable = supported_exts.at(ext.string());
const auto& host_path = std::filesystem::current_path() / host_executable;
if (!std::filesystem::exists(host_path)) {
throw std::runtime_error("未找到主机可执行文件:" + host_path.string());
}
// 使用最新的Boost.Process v2 API
try {
return std::make_unique<bp::process>(ctx, host_path.string(), bp::process::args_t{get_plugin_id_name(id)});
} catch (const std::exception& e) {
spdlog::error("Failed to create plugin process: {}", e.what());
throw;
}
}
plugin_instance::plugin_instance(boost::asio::io_context& ctx,
uint32_t in_id,
const std::filesystem::path& in_plugin_path) {
id = in_id;
// 创建共享内存段
// input_segment = std::make_unique<bi::managed_shared_memory>(bi::open_or_create, get_shm_in_name(id).c_str(), SHM_SIZE);
// output_segment = std::make_unique<bi::managed_shared_memory>(bi::open_or_create, get_shm_out_name(id).c_str(), SHM_SIZE);
// 获取锁无阻塞队列
// input_queue = input_segment->find_or_construct<lock_free_queue>("input_queue")();
// output_queue = output_segment->find_or_construct<lock_free_queue>("output_queue")();
plugin_node_.get_audio_rb().create_buffer(get_plugin_id_name(id), 10, 512); // 10块512采样点的缓冲区
process = execute_plugin_process(id, ctx, in_plugin_path);
if (!process || !is_process_running()) {
throw std::runtime_error("插件进程启动失败");
}
// 设置进程退出回调
process->async_wait([this](boost::system::error_code ec, int exit_code) {
if (!ec) {
spdlog::info("Plugin process {} exited with code {}", id, exit_code);
// 在这里可以处理进程退出事件
is_processing_done = true;
is_registered = false;
}
});
plugin_node_.open_queue(get_plugin_id_name(id));
}
plugin_instance::~plugin_instance() {
// 使用新的API进行优雅关闭
request_graceful_shutdown();
// 给予进程一定时间进行优雅关闭
boost::system::error_code ec;
if (process && process->is_open()) {
// 等待最多1秒
process->wait_for(std::chrono::seconds(1), ec);
if (ec) {
// 如果等待超时,强制终止
kill_process(false);
}
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <boost/process/v2.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include "ipc/ipc_node.h"
#include "rpc/common.h"
namespace bp = boost::process_v2;
struct plugin_instance {
plugin_instance(boost::asio::io_context& ctx, uint32_t in_id, const std::filesystem::path& in_plugin_path);
~plugin_instance();
uint32_t id = 0;
// std::unique_ptr<boost::interprocess::managed_shared_memory> input_segment;
// std::unique_ptr<boost::interprocess::managed_shared_memory> output_segment;
// lock_free_queue* input_queue = nullptr; // AudioEngine -> Host
// lock_free_queue* output_queue = nullptr; // Host -> AudioEngine
// 状态标识
std::atomic_bool is_processing_done{ true }; // 插件是否处理完毕
std::atomic_bool is_registered{ false }; // RPC会话是否注册
auto is_process_running() const {
return process && process->is_open();
}
void async_wait_for_exit(std::function<void(int)> callback) {
if (process && process->is_open()) {
process->async_wait([callback = std::move(callback)](boost::system::error_code ec, int exit_code) {
if (!ec) {
callback(exit_code);
}
});
}
}
void request_graceful_shutdown() {
if (process && process->is_open()) {
boost::system::error_code ec;
process->request_exit(ec);
if (!ec) {
// 优雅关闭请求已发送
}
}
}
void kill_process(bool wait) {
if (process && process->is_open()) {
boost::system::error_code ec;
process->terminate(ec);
if (wait && !ec) {
process->wait(ec);
}
}
process.reset();
}
plugin_ipc_remote_node plugin_node_;
private:
std::unique_ptr<bp::process> process; // 使用最新Boost.Process v2 API
};

View File

@@ -0,0 +1,5 @@
#include "engine_rpc.h"
namespace engine_rpc {
RPC_REG(LOG, log_impl)
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "rpc/rpc_type.h"
#include "rpc/rpc_manager.h"
namespace engine_rpc {
struct log_impl : log {
void process() {
const auto str = str_.get_string();
spdlog::log(level_, "[Sandbox] {}", str.c_str());
}
};
struct test {
uint32_t id;
void process() {
spdlog::info("收到测试消息ID: {}", id);
}
};
}

View File

@@ -16,6 +16,11 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
# @param ${SRC_FILES}: 构成该目标的所有源文件。
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
find_package(spdlog CONFIG REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
find_package(ZeroMQ REQUIRED)
# 将依赖库链接到我们的可执行文件目标。
# @param ${PROJECT_NAME}: 要链接的目标。
# @param PRIVATE: 表示链接的依赖项仅对 ${PROJECT_NAME} 自身可见,
@@ -25,5 +30,13 @@ add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
# @param protobuf::libprotobuf: Protobuf 运行时库目标。
# @param zmq: ZeroMQ 库目标。
target_link_libraries(${PROJECT_NAME} PRIVATE config_target)
target_link_libraries(${PROJECT_NAME} PUBLIC spdlog::spdlog)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_os_definitions(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PUBLIC
Boost::circular_buffer
Boost::uuid
Boost::thread
Boost::interprocess
Boost::lockfree
)

View File

@@ -0,0 +1 @@
#include "audio_ring_buffer.h"

View File

@@ -0,0 +1,746 @@
#pragma once
#include <atomic> // 用于原子操作,实现无锁并发
#include <chrono> // 用于生成唯一名称(扩容时)
#include <memory> // 用于智能指针和内存管理
#include <stdexcept> // 用于抛出运行时异常
#include <string> // 用于字符串操作
#include <type_traits> // 用于编译时类型检查 (static_assert)
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include "ipc/shm_mgr.h" // 假设这是共享内存管理的辅助类
namespace bip = boost::interprocess;
/**
* @brief 一个高性能、支持跨进程、可动态扩容的无锁音频环形缓冲区。
*
* 该类模板基于 Boost.Interprocess 共享内存实现,允许多个进程高效地共享音频数据流。
* 它采用了一个两阶段队列pending 和 processed来管理数据块的生命周期
* 适用于生产者-消费者模型,例如一个进程写入音频数据,另一个进程进行处理。
*
*核心特性:
* - **跨进程共享:** 数据存储在共享内存中,可供多个进程同时访问。
* - **无锁设计:** 使用 std::atomic 实现关键位置的无锁读写,避免了锁竞争带来的性能开销。
* - **动态扩容:** 当缓冲区或内部队列空间不足时,会自动进行扩容,增强了系统的鲁棒性。
* - **两阶段提交流程:**
* 1. `acquire_pending_block` -> `commit_pending_block`: 生产者获取并填充数据。
* 2. `get_pending_block` -> `complete_pending_block`: 中间处理者(可选)获取待处理数据并标记为完成。
* 3. `get_processed_block` -> `release_processed_block`: 消费者获取已处理数据并释放空间。
* - **类型安全:** 使用模板和 static_assert 确保存储的类型是可平凡复制的Trivially Copyable
* 这对于在共享内存中直接进行内存操作至关重要。
*
* @tparam T 缓冲区中存储的音频数据样本类型(如 float, int16_t
*/
template <typename T>
class audio_ring_buffer {
// 编译时检查,确保模板参数 T 是可以安全地在内存中直接复制的类型。
// 这是在共享内存中操作数据的基本要求。
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
public:
// --- 公共类型定义与结构体 (Public Types & Structs) ---
/**
* @brief 代表一个音频数据块的结构体。
*
* 作为与缓冲区交互的基本单位,包含了指向数据的指针、数据大小和时间戳。
*/
struct audio_block {
T* data; // 指向音频数据在共享内存中的起始位置
size_t size; // 数据块的大小(以元素 T 的数量计)
uint64_t timestamp; // 数据块关联的时间戳
/**
* @brief 默认构造函数
*/
audio_block() : data(nullptr), size(0), timestamp(0) {}
/**
* @brief 带参数的构造函数
* @param ptr 指向数据的指针
* @param sz 数据块大小
* @param ts 时间戳
*/
audio_block(T* ptr, const size_t sz, const uint64_t ts = 0)
: data(ptr), size(sz), timestamp(ts) {}
};
/**
* @brief 包含缓冲区当前状态的统计信息。
*/
struct buffer_stats {
size_t queue_capacity; // 内部队列的容量
size_t audio_buffer_size; // 音频数据缓冲区的总大小(元素数)
size_t pending_count; // 等待处理的数据块数量
size_t processed_count; // 已处理完成、等待消费的数据块数量
size_t available_space; // 音频缓冲区中可用的剩余空间(元素数)
};
private:
// --- 私有辅助结构体 (Private Helper Structs) ---
/**
* @brief 存储在共享内存中的控制块。
*
* 包含所有环形缓冲区的状态信息和原子指针,用于协调跨进程读写。
*/
struct shared_control_block {
// 原子变量,用于无锁队列和缓冲区的位置跟踪
std::atomic<size_t> pending_write_pos{ 0 }; // "待处理"队列的写指针
std::atomic<size_t> pending_read_pos{ 0 }; // "待处理"队列的读指针
std::atomic<size_t> processed_write_pos{ 0 }; // "已处理"队列的写指针
std::atomic<size_t> processed_read_pos{ 0 }; // "已处理"队列的读指针
std::atomic<size_t> audio_write_pos{ 0 }; // 音频数据环形缓冲区的写指针
std::atomic<size_t> audio_read_pos{ 0 }; // 音频数据环形缓冲区的读指针
// 静态容量信息
size_t queue_capacity{}; // 队列容量 (pending 和 processed 队列大小相同)
size_t audio_buffer_size{}; // 音频数据缓冲区的总容量
// TODO: 这个参数似乎没有在当前逻辑中使用,可以考虑移除或实现相关功能。
size_t block_size{}; // 初始块大小
char initialized{}; // 初始化标志,用于确保共享内存只被初始化一次
shared_control_block() = default;
shared_control_block(size_t qcap, size_t abuf_size, size_t bsize)
: queue_capacity(qcap),
audio_buffer_size(abuf_size),
block_size(bsize),
initialized(1) {}
};
/**
* @brief 内部队列的条目定义。
*
* 描述了一个已提交的音频块在主音频缓冲区中的位置和元数据。
*/
struct queue_entry {
size_t audio_offset; // 数据在 audio_buffer_ 中的偏移量
size_t size; // 数据块的大小
uint64_t timestamp; // 时间戳
queue_entry() = default;
queue_entry(size_t offset, size_t sz, uint64_t ts)
: audio_offset(offset), size(sz), timestamp(ts) {}
};
// --- 私有成员变量 (Private Member Variables) ---
shm_block audio_shm_; // 共享内存管理器实例
std::string buffer_name_; // 缓冲区的唯一名称标识符
struct shm_data {
shm_obj_t<shared_control_block> control;
shm_obj_t<queue_entry> pending_queue;
shm_obj_t<queue_entry> processed_queue;
shm_obj_t<T> audio_buffer;
[[nodiscard]] bool is_valid() const {
return !control.is_null() && !pending_queue.is_null() && !processed_queue.is_null() && !audio_buffer.is_null();
}
} *data;
public:
// --- 构造、析构与生命周期管理 (Constructors, Destructor & Lifetime) ---
audio_ring_buffer() = default;
~audio_ring_buffer() = default;
// 移动构造函数:高效转移资源所有权
audio_ring_buffer(audio_ring_buffer&& other) noexcept : audio_shm_(std::move(other.audio_shm_)),
buffer_name_(std::move(other.buffer_name_)),
data(other.data) {
// 将源对象的指针置空,防止其析构时释放资源
other.shm_data.control = {};
}
// 移动赋值运算符
audio_ring_buffer& operator=(audio_ring_buffer&& other) noexcept {
if (this != &other) {
audio_shm_ = other.audio_shm_;
buffer_name_ = std::move(other.buffer_name_);
data = other.data;
// 将源对象的指针置空
other.shm_data.control = {};
}
return *this;
}
// 禁用拷贝构造函数和拷贝赋值运算符,因为该类管理着独特的共享内存资源
audio_ring_buffer(const audio_ring_buffer&) = delete;
audio_ring_buffer& operator=(const audio_ring_buffer&) = delete;
/**
* @brief 创建一个新的共享音频缓冲区。
*
* 功能描述: 作为生产者或服务主控方,调用此函数来初始化共享内存区域和所有控制结构。
* @param in_name 缓冲区的唯一名称,用于其他进程连接。
* @param block_count 内部队列的初始容量(可以容纳多少个数据块)。
* @param block_size 单个数据块的典型大小(用于计算初始总缓冲区大小)。
* @returns 如果创建成功返回 true否则返回 false。
* @example
* audio_ring_buffer<float> buffer;
* if (buffer.create_buffer("my_audio_stream", 100, 1024)) {
* // 创建成功
* }
*/
bool create_buffer(const std::string& in_name,
size_t block_count, // 块数量
size_t block_size) { // 单块大小(以 T 元素计)
// 将用户提供的直观参数(块数量、块大小)映射到内部实现所需的参数
const size_t queue_capacity = block_count;
const size_t audio_buffer_capacity = block_count * block_size;
audio_shm_ = shm_mgr::ab_shm();
buffer_name_ = in_name;
initialize_shared_structures(queue_capacity, audio_buffer_capacity, block_size);
return data->is_valid();
}
/**
* @brief 打开一个已存在的共享音频缓冲区。
*
* 功能描述: 作为消费者或后加入的进程,调用此函数来连接到由 create_buffer 创建的共享内存。
* @param in_name 要连接的缓冲区的唯一名称。
* @returns 如果连接成功返回 true否则返回 false。
* @example
* audio_ring_buffer<float> consumer_buffer;
* if (consumer_buffer.open_buffer("my_audio_stream")) {
* // 连接成功
* }
*/
bool open_buffer(const std::string& in_name) {
audio_shm_ = shm_mgr::ab_shm();
buffer_name_ = in_name;
connect_to_existing_structures();
return data->is_valid();
}
// --- 核心写入操作 (Core Write Operations) ---
/**
* @brief (生产者) 获取一个可写入的音频块。
*
* 功能描述: 这是写入数据的第一步。请求一个指定大小的内存块用于写入。
* 设计思路:
* 1. 检查环形缓冲区是否有足够的连续空间。
* 2. 如果空间不足,会触发自动扩容机制 `try_expand_audio_buffer`。
* 3. 如果空间足够但写指针接近末尾(即需要环绕),则会尝试将写指针移到开头。
* 4. 返回一个 `audio_block` 结构,包含了可写入的内存地址和大小。
*
* @param required_size 需要写入的元素数量。
* @returns 一个有效的 audio_block。如果当前无法分配足够空间则返回一个 data 为 nullptr 的无效 block。
* @example
* auto block = buffer.acquire_pending_block(512);
* if (block.data) {
* // 成功获取,可以向 block.data 写入 512 个 T 类型的元素
* }
*/
audio_block acquire_pending_block(size_t required_size) {
auto write_pos = data->control->audio_write_pos.load(std::memory_order_acquire);
auto read_pos = data->control->audio_read_pos.load(std::memory_order_acquire);
auto available = calculate_available_space(write_pos, read_pos);
// 如果当前可用空间不足,尝试扩展音频数据缓冲区
if (available < required_size) {
if (!try_expand_audio_buffer(required_size)) {
return audio_block(); // 扩展失败,返回无效块
}
// 扩容后,重新获取指针和可用空间
write_pos = data->control->audio_write_pos.load(std::memory_order_acquire);
read_pos = data->control->audio_read_pos.load(std::memory_order_acquire); // read_pos 也可能因数据整理而改变
available = calculate_available_space(write_pos, read_pos);
}
// 检查在当前写指针位置是否有足够的连续空间
if (write_pos + required_size > data->control->audio_buffer_size) {
// 如果尾部空间不足,检查头部是否有足够空间(即环绕)
if (read_pos >= required_size) {
// 可以安全地环绕到缓冲区开头
write_pos = 0;
// 注意:这里暂时不更新共享内存中的 write_pos因为这只是一个预分配。
// 真正的更新发生在 commit_pending_block 中。
} else {
// 头部空间也不足,无法分配
return audio_block();
}
}
return audio_block(data->audio_buffer.to_local() + write_pos, required_size);
}
/**
* @brief (生产者) 提交一个已写入的音频块。
*
* 功能描述: 这是写入数据的第二步。在向 `acquire_pending_block` 获取的内存中写入数据后,
* 调用此函数将其提交到 "待处理" (pending) 队列中,以供后续处理。
* 设计思路:
* 1. 将数据块的元信息(偏移、大小、时间戳)存入 pending_queue_。
* 2. 原子地更新音频缓冲区的写指针 `audio_write_pos` 和 pending 队列的写指针 `pending_write_pos`。
* 3. 如果 pending 队列已满,会触发 `try_expand_queues` 进行扩容。
*
* @param block 从 `acquire_pending_block` 获取并已填充数据的 audio_block。
* @param timestamp 关联的时间戳。
* @returns 如果提交成功返回 true。
* @example
* auto block = buffer.acquire_pending_block(512);
* if (block.data) {
* fill_audio_data(block.data, 512);
* buffer.commit_pending_block(block, get_current_timestamp());
* }
*/
bool commit_pending_block(const audio_block& block, uint64_t timestamp = 0) {
auto queue_write = data->control->pending_write_pos.load(std::memory_order_acquire);
auto queue_read = data->control->pending_read_pos.load(std::memory_order_acquire);
// 检查 "待处理" 队列是否已满
if (is_queue_full(queue_write, queue_read)) {
if (!try_expand_queues()) {
return false; // 队列扩容失败
}
// 扩容后重新获取指针
queue_write = data->control->pending_write_pos.load(std::memory_order_acquire);
queue_read = data->control->pending_read_pos.load(std::memory_order_acquire);
}
// 计算数据在缓冲区中的偏移量
auto audio_offset = block.data - data->audio_buffer.to_local();
// 在队列中创建一个新条目
data->pending_queue[queue_write] = queue_entry(audio_offset, block.size, timestamp);
// 更新音频数据缓冲区的真实写指针
data->control->audio_write_pos.store(audio_offset + block.size, std::memory_order_release);
// 原子地推进 "待处理" 队列的写指针
auto next_write = (queue_write + 1) % control_->queue_capacity;
control_->pending_write_pos.store(next_write, std::memory_order_release);
return true;
}
// --- 核心处理与读取操作 (Core Processing & Read Operations) ---
/**
* @brief (处理器) 获取一个待处理的音频块。
*
* 功能描述: 从 "待处理" (pending) 队列头部获取一个数据块用于处理,但并不将其从队列中移除。
* @returns 如果队列不为空,返回一个有效的 audio_block否则返回无效块。
* @example
* auto block_to_process = buffer.get_pending_block();
* if (block_to_process.data) {
* // 处理数据...
* }
*/
audio_block get_pending_block() {
auto queue_read = control_->pending_read_pos.load(std::memory_order_acquire);
auto queue_write = control_->pending_write_pos.load(std::memory_order_acquire);
if (queue_read == queue_write) {
return audio_block(); // 队列为空
}
const queue_entry& entry = pending_queue_[queue_read];
return audio_block(audio_buffer_ + entry.audio_offset, entry.size, entry.timestamp);
}
/**
* @brief (处理器) 完成一个待处理音频块的加工。
*
* 功能描述: 将 `get_pending_block` 获取到的数据块从 "待处理" 队列移至 "已处理" (processed) 队列。
* 这标志着该数据块已处理完毕,可供最终消费者使用。
* 设计思路:
* 1. 从 pending 队列头部读取条目。
* 2. 将该条目复制到 processed 队列的尾部。
* 3. 原子地推进 pending 队列的读指针和 processed 队列的写指针。
* 4. 如果 processed 队列已满,会触发扩容。
*
* @returns 如果操作成功返回 true。
* @example
* auto block_to_process = buffer.get_pending_block();
* if (block_to_process.data) {
* process_audio(block_to_process.data, block_to_process.size);
* buffer.complete_pending_block();
* }
*/
bool complete_pending_block() {
auto pending_read = control_->pending_read_pos.load(std::memory_order_acquire);
auto pending_write = control_->pending_write_pos.load(std::memory_order_acquire);
if (pending_read == pending_write) {
return false; // "待处理" 队列为空
}
auto processed_write = control_->processed_write_pos.load(std::memory_order_acquire);
auto processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
// 如果 "已处理" 队列满了,尝试扩展
if (is_queue_full(processed_write, processed_read)) {
if (!try_expand_queues()) {
return false;
}
// 扩容后重新获取指针
processed_write = control_->processed_write_pos.load(std::memory_order_acquire);
processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
}
// 将条目从 pending 队列移动到 processed 队列
processed_queue_[processed_write] = pending_queue_[pending_read];
// 原子地更新两个队列的指针
auto next_pending_read = (pending_read + 1) % control_->queue_capacity;
auto next_processed_write = (processed_write + 1) % control_->queue_capacity;
control_->pending_read_pos.store(next_pending_read, std::memory_order_release);
control_->processed_write_pos.store(next_processed_write, std::memory_order_release);
return true;
}
/**
* @brief (消费者) 获取一个已处理的音频块。
*
* 功能描述: 从 "已处理" (processed) 队列头部获取一个数据块用于消费(如播放),但并不释放其空间。
* @returns 如果队列不为空,返回一个有效的 audio_block否则返回无效块。
* @example
* auto block_to_play = buffer.get_processed_block();
* if (block_to_play.data) {
* // 播放音频...
* }
*/
audio_block get_processed_block() {
auto queue_read = control_->processed_read_pos.load(std::memory_order_acquire);
auto queue_write = control_->processed_write_pos.load(std::memory_order_acquire);
if (queue_read == queue_write) {
return audio_block(); // 队列为空
}
const queue_entry& entry = processed_queue_[queue_read];
return audio_block(audio_buffer_ + entry.audio_offset, entry.size, entry.timestamp);
}
/**
* @brief (消费者) 释放一个已处理的音频块。
*
* 功能描述: 在消费完 `get_processed_block` 获取的数据后,调用此函数来释放其占用的空间。
* 设计思路:
* 1. 从 processed 队列头部移除条目(通过移动读指针)。
* 2. 将音频数据缓冲区的读指针 `audio_read_pos` 更新到被释放块的末尾。
* 这使得这部分空间可以被生产者重新使用。
*
* @returns 如果操作成功返回 true。
* @example
* auto block_to_play = buffer.get_processed_block();
* if (block_to_play.data) {
* play_audio(block_to_play.data, block_to_play.size);
* buffer.release_processed_block();
* }
*/
bool release_processed_block() {
auto queue_read = control_->processed_read_pos.load(std::memory_order_acquire);
auto queue_write = control_->processed_write_pos.load(std::memory_order_acquire);
if (queue_read == queue_write) {
return false; // "已处理" 队列为空
}
const queue_entry& entry = processed_queue_[queue_read];
// 更新音频数据缓冲区的读指针,这步操作"释放"了环形缓冲区中的空间
auto expected_read_pos = entry.audio_offset + entry.size;
control_->audio_read_pos.store(expected_read_pos, std::memory_order_release);
// 原子地推进 "已处理" 队列的读指针
auto next_read = (queue_read + 1) % control_->queue_capacity;
control_->processed_read_pos.store(next_read, std::memory_order_release);
return true;
}
// --- 状态查询与控制 (Status Query & Control) ---
/**
* @brief 获取待处理队列中的块数量。
* @returns 待处理的块数。
*/
[[nodiscard]] size_t pending_count() const {
auto write = control_->pending_write_pos.load(std::memory_order_acquire);
auto read = control_->pending_read_pos.load(std::memory_order_acquire);
return write >= read ? write - read : control_->queue_capacity - read + write;
}
/**
* @brief 获取已处理队列中的块数量。
* @returns 已处理的块数。
*/
[[nodiscard]] size_t processed_count() const {
auto write = control_->processed_write_pos.load(std::memory_order_acquire);
auto read = control_->processed_read_pos.load(std::memory_order_acquire);
return write >= read ? write - read : control_->queue_capacity - read + write;
}
/**
* @brief 获取音频缓冲区中可用的总空间。
* @returns 可用空间大小(以元素 T 计)。
*/
[[nodiscard]] size_t available_audio_space() const {
auto write = control_->audio_write_pos.load(std::memory_order_acquire);
auto read = control_->audio_read_pos.load(std::memory_order_acquire);
return calculate_available_space(write, read);
}
/**
* @brief 重置整个缓冲区状态。
* @warning 这是一个危险操作,会清空所有队列和数据,可能导致数据丢失。
* 只应在确定没有其他进程正在使用缓冲区时调用。
*/
void reset() {
control_->pending_write_pos.store(0, std::memory_order_release);
control_->pending_read_pos.store(0, std::memory_order_release);
control_->processed_write_pos.store(0, std::memory_order_release);
control_->processed_read_pos.store(0, std::memory_order_release);
control_->audio_write_pos.store(0, std::memory_order_release);
control_->audio_read_pos.store(0, std::memory_order_release);
}
/**
* @brief 获取缓冲区的综合统计信息。
* @returns 一个 buffer_stats 结构体实例。
*/
[[nodiscard]] buffer_stats get_stats() const {
return { control_->queue_capacity,
control_->audio_buffer_size,
pending_count(),
processed_count(),
available_audio_space() };
}
// --- 兼容性接口 (Compatibility Interfaces) ---
/**
* @brief 获取底层的 Boost.Interprocess segment_manager。
* @returns 指向 segment_manager 的指针。
*/
[[nodiscard]] auto get_segment_manager() const {
return audio_shm_.get_segment_manager();
}
private:
// --- 私有辅助函数 (Private Helper Functions) ---
/**
* @brief 初始化所有共享内存中的数据结构。
*/
void initialize_shared_structures(size_t queue_capacity,
size_t audio_buffer_capacity,
size_t block_size) {
auto control = audio_shm_.construct<shared_control_block>(queue_capacity, audio_buffer_capacity, block_size);
auto pending_queue = audio_shm_.construct<queue_entry>();
auto processed_queue = audio_shm_.construct<queue_entry>();
auto audio_buffer = audio_shm_.construct<T>(audio_buffer_capacity);
data = audio_shm_.construct_with_name<shm_data>(buffer_name_);
data->control = control;
data->pending_queue = pending_queue;
data->processed_queue = processed_queue;
data->audio_buffer = audio_buffer;
}
/**
* @brief 连接到已存在的共享内存数据结构。
*/
void connect_to_existing_structures() {
data = audio_shm_.find<shm_data>(buffer_name_);
if (!data) {
throw std::runtime_error("Failed to connect to existing shared memory structures");
}
}
/**
* @brief 当音频缓冲区空间不足时尝试进行扩容。
*
* 设计思路:
* 1. 计算新容量:目标大小至少是当前大小的两倍,且必须能容纳本次请求的数据。
* 2. 创建新缓冲区:在共享内存中创建一个新的、更大的 T 类型数组。
* 3. 整理并拷贝数据:将旧缓冲区中的有效数据(可能存在环绕)线性地拷贝到新缓冲区的起始位置。
* 4. 更新状态:更新 control_ 块中的缓冲区大小、读写指针,并用新缓冲区替换旧的。
* 5. 释放旧缓冲区(由 shm_mgr 负责)。
*
* @param required_size 至少需要额外容纳的元素数量。
* @returns 扩容成功返回 true失败如共享内存不足返回 false。
* @note 这是一个成本较高的操作,应尽量在初始化时分配合理大小以避免频繁扩容。
*/
// TODO: 这个函数逻辑较复杂,可以考虑拆分为更小的私有函数,
// 例如: `calculate_new_buffer_size`, `reorganize_and_copy_data`。
bool try_expand_audio_buffer(size_t required_size) {
const size_t cur_size = control_->audio_buffer_size;
const size_t write_pos = control_->audio_write_pos.load(std::memory_order_acquire);
const size_t read_pos = control_->audio_read_pos.load(std::memory_order_acquire);
// --- 1. 计算新容量和已用空间 ---
const size_t used =
write_pos >= read_pos ? write_pos - read_pos : cur_size - read_pos + write_pos;
size_t new_size = cur_size > 0 ? cur_size : 1;
// 扩容策略:每次翻倍,直到能容纳 (已用空间 + 新增需求 + 1个哨兵位)。
while (new_size - used - 1 < required_size) {
new_size <<= 1;
}
// --- 2. 创建新的共享内存缓冲区 ---
const std::string new_name = buffer_name_ + "_AudioBuffer_" + std::to_string(new_size);
T* new_buf = audio_shm_.find_or_construct<T>(new_name, new_size);
if (!new_buf) {
// log error: ("Audio buffer expand failed: not enough shared memory");
return false;
}
// --- 3. 拷贝旧数据到新缓冲区(数据线性化)---
if (used > 0) {
if (write_pos > read_pos) { // 情况一:数据是连续的
std::memcpy(new_buf, audio_buffer_ + read_pos, used * sizeof(T));
} else { // 情况二:数据被环绕,分两段拷贝
// 拷贝 read_pos 到末尾的数据
const size_t tail_size = cur_size - read_pos;
std::memcpy(new_buf, audio_buffer_ + read_pos, tail_size * sizeof(T));
// 拷贝开头到 write_pos 的数据
std::memcpy(new_buf + tail_size, audio_buffer_, write_pos * sizeof(T));
}
}
// TODO: 需要一种机制来销毁旧的 audio_buffer_ 对象以释放共享内存。
// 目前依赖于 shm_mgr 的实现,假设它能处理同名对象的替换。
// 例如:`audio_shm_.destroy<T>(buffer_name_ + "_AudioBuffer");`
// --- 4. 原子地更新所有指针和状态 ---
audio_buffer_ = new_buf;
control_->audio_buffer_size = new_size;
control_->audio_read_pos.store(0, std::memory_order_release); // 新缓冲区中,数据从 0 开始
control_->audio_write_pos.store(used, std::memory_order_release); // 写指针指向数据末尾
// log info: ("Audio buffer '{}' expanded: {} -> {} elements", buffer_name_, cur_size, new_size);
return true;
}
/**
* @brief 当内部队列满时尝试进行扩容。
*
* 设计思路:
* 1. 计算新容量(通常是当前的两倍)。
* 2. 创建新的、更大的 pending 和 processed 队列。
* 3. 将旧队列中的数据完整拷贝到新队列。
* 4. 更新内部指针指向新队列,并更新 control_ 块中的队列容量。
*
* @returns 扩容成功返回 true失败返回 false。
*/
bool try_expand_queues() {
const size_t cur_capacity = control_->queue_capacity;
const size_t new_capacity = cur_capacity * 2;
const size_t pending_read = control_->pending_read_pos.load(std::memory_order_acquire);
const size_t pending_write = control_->pending_write_pos.load(std::memory_order_acquire);
const size_t processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
const size_t processed_write =
control_->processed_write_pos.load(std::memory_order_acquire);
// --- 为队列创建临时唯一名称以避免冲突 ---
auto get_unique_name = [&](const std::string& base) {
return base + "_Expanded_" +
std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count());
};
// --- 1. 创建新队列 ---
const std::string new_pending_name = get_unique_name(buffer_name_ + "_PendingQueue");
auto* new_pending_queue = audio_shm_.find_or_construct<queue_entry>(new_pending_name);
if (!new_pending_queue) return false;
const std::string new_processed_name = get_unique_name(buffer_name_ + "_ProcessedQueue");
auto* new_processed_queue = audio_shm_.find_or_construct<queue_entry>(new_processed_name);
if (!new_processed_queue) {
// 如果第二个队列创建失败,需要清理已创建的第一个队列
// audio_shm_.destroy<queue_entry>(new_pending_name);
return false;
}
// --- 2. 拷贝数据并线性化 ---
// 拷贝 pending 队列
size_t count = 0;
for (size_t i = pending_read; i != pending_write; i = (i + 1) % cur_capacity) {
new_pending_queue[count++] = pending_queue_[i];
}
// 更新 pending 队列指针
control_->pending_read_pos.store(0, std::memory_order_release);
control_->pending_write_pos.store(count, std::memory_order_release);
// 拷贝 processed 队列
count = 0;
for (size_t i = processed_read; i != processed_write; i = (i + 1) % cur_capacity) {
new_processed_queue[count++] = processed_queue_[i];
}
// 更新 processed 队列指针
control_->processed_read_pos.store(0, std::memory_order_release);
control_->processed_write_pos.store(count, std::memory_order_release);
// --- 3. 更新内部指针和容量 ---
pending_queue_ = new_pending_queue;
processed_queue_ = new_processed_queue;
control_->queue_capacity = new_capacity;
// log info: ("Queue capacity expanded to {} for buffer: {}", new_capacity, buffer_name_);
return true;
}
/**
* @brief 计算环形缓冲区中的可用空间。
*/
[[nodiscard]] size_t calculate_available_space(size_t write_pos,
size_t read_pos) const {
if (write_pos >= read_pos) {
// 写指针在前,可用空间是尾部剩余 + 头部已读 - 1哨兵位
return control_->audio_buffer_size - write_pos + read_pos - 1;
}
// 读指针在前(环绕),可用空间是两者之间的部分 - 1哨兵位
return read_pos - write_pos - 1;
}
/**
* @brief 检查环形队列是否已满。
*/
[[nodiscard]] bool is_queue_full(size_t write_pos, size_t read_pos) const {
return ((write_pos + 1) % control_->queue_capacity) == read_pos;
}
};
// --- 工厂类 (Factory Class) ---
// 注意:以下工厂类提供了一种创建和连接缓冲区的替代方式。
// 随着 C++ 的发展,直接使用类本身的 create_buffer / open_buffer 成员函数
// 可能是更现代和清晰的用法。此工厂模式在此处保留,可能用于兼容旧代码或特定设计模式。
template <typename T>
class audio_ring_buffer_factory {
public:
/**
* @brief 创建一个新的 audio_ring_buffer 实例。
* @note 此函数已废弃,建议直接使用 `audio_ring_buffer<T>::create_buffer`。
*/
[[deprecated("Use audio_ring_buffer<T>::create_buffer instead.")]] static audio_ring_buffer<T>
create_new(const std::string& buffer_name, size_t block_count, size_t block_size) {
audio_ring_buffer<T> buffer;
buffer.create_buffer(buffer_name, block_count, block_size);
return buffer;
}
/**
* @brief 连接到一个已存在的 audio_ring_buffer 实例。
* @note 此函数已废弃,建议直接使用 `audio_ring_buffer<T>::open_buffer`。
*/
[[deprecated("Use audio_ring_buffer<T>::open_buffer instead.")]] static audio_ring_buffer<T>
connect_existing(const std::string& buffer_name) {
audio_ring_buffer<T> buffer;
buffer.open_buffer(buffer_name);
return buffer;
}
};

View File

@@ -0,0 +1,8 @@
/*
* 文件: ipc_node.cpp
* 说明: ipc_node 相关成员实现,主要聚焦共享内存队列的创建/销毁与消息处理。
*/
#include "ipc_node.h"
#include <stdexcept> // std::runtime_error
#include <utility> // std::move

View File

@@ -0,0 +1,245 @@
/*
* 文件: ipc_node.h
* 功能: 提供进程/插件之间的 IPC基于共享内存 + 无锁环形队列)节点封装,
* 并对外暴露简易的 RPC 调用与处理接口。
*
* 主要组件:
* 1. ipc_node —— 共享内存队列的最小包装,负责创建 / 打开 / 销毁队列,
* 并将 rpc_message_t 写入或读取出来。
* 2. engine_ipc_node —— 引擎侧封装,负责创建核心队列 "engine_queue" 并轮询处理。
* 3. plugin_ipc_node —— 插件侧封装,负责连接 "engine_queue" 并创建自身专用队列。
*
* ⚠️ 注意:
* • 所有队列均为单生产者单消费者 (SPSC)capacity 目前硬编码为 1024。
* • 调用顺序: 先 create_queue / open_queue再进行 push/pop。
* • 队列在 owner 析构时会自动销毁,确保共享内存资源不泄漏。
*/
#pragma once
// --- 头文件导入 (Headers) ----------------------------------------------------
#include <string>
#include <cstddef> // std::byte
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/container/vector.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/lockfree/queue.hpp>
#include "audio_ring_buffer.h"
#include "ipc_queue.h"
#include "misc_type.h"
#include "shm_mgr.h"
#include "rpc/rpc_manager.h"
namespace bi = boost::interprocess;
namespace bc = boost::container;
// --- 基础节点 (ipc_node) -----------------------------------------------------
using ipc_msg_queue_t = ipc_queue<rpc_message_t>;
/**
* @class ipc_node
* @brief 对 boost::lockfree::spsc_queue 的一层轻量包装,
* 负责共享内存生命周期和基本 RPC push/pop。
*/
class ipc_node {
public:
friend class engine_ipc_node; // 引擎端友元,可直接访问 rpc_queue
friend class plugin_ipc_node; // 插件端友元
friend class plugin_ipc_remote_node; // 插件端远程调用节点友元
/**
* @brief 在共享内存中创建一条新的 SPSC 队列
* @param in_name 队列名字 (唯一 key)
* @throws std::runtime_error 创建失败时抛出
*/
void create_queue(const std::string& in_name) {
rpc_queue.create_queue(in_name);
}
/**
* @brief 打开 (attach) 已存在的 SPSC 队列
* @param in_name 队列名字
* @throws std::runtime_error 打开失败时抛出
*/
void open_queue(const std::string& in_name) {
rpc_queue.open_queue(in_name, std::chrono::seconds(10));
}
template<typename T>
struct msg_proxy {
rpc_message_t msg;
ipc_msg_queue_t* queue;
msg_proxy(ipc_msg_queue_t* in_queue) : queue(in_queue) {
auto message_id = T::rpc_id;
msg.message_id = message_id;
msg.make_data<T>();
}
void submit() const {
if (!queue->push(msg)) {
// 队列满了,丢弃消息并记录日志
spdlog::warn("IPC队列满丢弃消息 ID: {}", static_cast<uint32_t>(msg.message_id));
}
}
T* operator->() const {
return msg.get_data<T>();
}
};
/**
* @tparam Msg 任意带有静态成员 `static constexpr rpc::message_type rpc_id`
* @param in_msg 消息实例
* @brief 将消息序列化为 rpc_message_t 并推入队列,实现“火并忘”式 RPC。
*
* 实现细节:
* 1. 通过 reinterpret_cast + memcpy 直接拷贝二进制内存到 payload
* 前提是 Msg 的二进制布局可跨进程使用POD or 标准布局)。
* 2. 此处为 **拷贝** 行为,若 Msg 过大可能影响性能,
* 可考虑改用共享指针或零拷贝方案 (TODO)。
*
* @code
* struct Ping { static constexpr auto rpc_id = rpc::message_type::Ping; };
* engine_node.call_rpc(Ping{});
* @endcode
*/
template<typename Msg>
auto call_rpc()
{
msg_proxy<Msg> proxy{&rpc_queue};
return proxy;
}
/**
* @brief 轮询队列并分发全部消息
*
* 设计:采用 while(pop) 循环,直到当前队列为空。
*/
void process_rpc() {
rpc_message_t msg;
while (rpc_queue.pop(msg)) {
// 💡 此处零分支,直接交由全局分发器; 出队顺序即调用顺序
rpc_message_handler::get_instance().handle_message(msg);
}
}
protected:
ipc_msg_queue_t rpc_queue; // 指向共享内存队列
ipc_node() = default; // 仅允许友元类/本类实例化
private:
// 禁止拷贝/移动,防止多重析构
ipc_node(const ipc_node&) = delete;
ipc_node& operator=(const ipc_node&) = delete;
};
// --- 引擎端包装 (engine_ipc_node) -------------------------------------------
/**
* @class engine_ipc_node
* @brief 引擎侧包装:
* • 创建 "engine_queue"
* • 提供 process_rpc() 供外部循环调用
*/
class engine_ipc_node {
public:
engine_ipc_node() { engine_queue.create_queue("engine_queue"); }
// 转调底层队列的消息处理
void process_rpc() { engine_queue.process_rpc(); }
private:
ipc_node engine_queue;
};
// --- 插件端包装 (plugin_ipc_node) -------------------------------------------
/**
* @class plugin_ipc_node
* @brief 插件侧包装:
* • 连接到引擎端 "engine_queue"
* • 创建自身专属队列 <in_queue_name>
*/
class plugin_ipc_node {
public:
explicit plugin_ipc_node(const std::string& in_queue_name): audio_rb() {
engine_queue.open_queue("engine_queue");
plugin_queue.create_queue(in_queue_name);
audio_rb.open_buffer(in_queue_name);
engine_heartbeat_ = shm_mgr::msg_shm().find<boost::atomic<heartbeat_t>>("engine_heartbeat");
if (!engine_heartbeat_) { throw std::runtime_error("无法找到 engine_running 标志"); }
plugin_heartbeat_ = shm_mgr::msg_shm().construct_with_name<boost::atomic<heartbeat_t>>(in_queue_name + "_heartbeat");
*plugin_heartbeat_ = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
// ------------------- RPC 调用 -------------------
template<typename Msg>
auto call_rpc() { return engine_queue.call_rpc<Msg>(); }
// ------------------- RPC 处理 -------------------
void process_rpc() { plugin_queue.process_rpc(); }
[[nodiscard]] auto is_engine_running() const {
// 读取引擎心跳
const auto& val = engine_heartbeat_->load();
if (val == 0) {
return false; // 引擎已退出
}
// 心跳阈值 (2 秒)
static constexpr auto threshold = std::chrono::seconds(2);
// 心跳间隔超过阈值则视为引擎已崩溃
const auto duration = std::chrono::high_resolution_clock::now().time_since_epoch() - heartbeat_duration_t(val);
return duration < threshold;
}
auto& get_audio_rb() { return audio_rb; }
private:
ipc_node engine_queue; // 指向引擎主队列(只写)
ipc_node plugin_queue; // 插件自队列(只读)
boost::atomic<heartbeat_t>* engine_heartbeat_;
boost::atomic<heartbeat_t>* plugin_heartbeat_;
audio_ring_buffer<float> audio_rb;
};
/**
* @class plugin_ipc_remote_node
* @brief 在Audio Engine中的代理节点, 用于发送远程RPC调用到插件进程
*/
class plugin_ipc_remote_node {
public:
plugin_ipc_remote_node() : audio_rb() {}
template<typename Msg>
auto call_rpc() { return plugin_queue.call_rpc<Msg>(); }
void open_queue(const std::string& in_queue_name) {
plugin_queue.open_queue(in_queue_name);
plugin_heartbeat_ = shm_mgr::msg_shm().find<boost::atomic<heartbeat_t>>(in_queue_name + "_heartbeat");
audio_rb.open_buffer(in_queue_name);
}
[[nodiscard]] auto is_timeout(int32_t in_seconds) const {
if (!plugin_heartbeat_) {
return true;
}
const auto& val = plugin_heartbeat_->load();
if (val == 0) {
return true; // 插件已退出
}
// 心跳阈值 (5 秒)
const auto& threshold = std::chrono::seconds(in_seconds);
// 心跳间隔超过阈值则视为插件已超时
const auto duration = std::chrono::high_resolution_clock::now().time_since_epoch() - heartbeat_duration_t(val);
return duration >= threshold;
}
[[nodiscard]] auto& get_audio_rb() { return audio_rb; }
private:
ipc_node plugin_queue{};
boost::atomic<heartbeat_t>* plugin_heartbeat_ = nullptr;
audio_ring_buffer<float> audio_rb;
};

View File

@@ -0,0 +1 @@
#include "ipc_queue.h"

View File

@@ -0,0 +1,149 @@
#pragma once
#include <boost/interprocess/containers/deque.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include "shm_mgr.h"
#include "rpc/common.h"
// 线程安全的消息队列包装器
template<typename T>
class ipc_queue {
private:
using allocator_t = allocator_t<T>;
using ipc_msg_deque_t = boost::interprocess::deque<T, allocator_t>;
ipc_msg_deque_t* deque_;
boost::interprocess::interprocess_mutex* mutex_;
bool is_owner_; // 是否由本实例创建
std::string name_;
public:
// 在共享内存中构造
ipc_queue(): deque_(nullptr),
mutex_(nullptr),
is_owner_(false) {
}
~ipc_queue() {
if (is_owner_) {
destroy();
}
}
void create_queue(const std::string& in_name) {
name_ = in_name;
const auto& block = shm_mgr::msg_shm();
const auto& queue_name = get_queue_name();
const auto& mutex_name = get_mutex_name();
destroy();
deque_ = block.construct_with_name<ipc_msg_deque_t>(queue_name, block.make_allocator<allocator_t>());
if (!deque_) {
throw std::runtime_error("无法创建IPC队列");
}
mutex_ = block.construct_with_name<boost::interprocess::interprocess_mutex>(mutex_name);
if (!mutex_) {
throw std::runtime_error("无法创建IPC队列锁");
}
is_owner_ = true;
}
void open_queue(const std::string& in_name, const std::optional<std::chrono::milliseconds> timeout = std::nullopt) {
name_ = in_name;
const auto& block = shm_mgr::msg_shm();
const auto& queue_name = get_queue_name();
const auto& mutex_name = get_mutex_name();
const auto& start_time = std::chrono::steady_clock::now();
// 查找队列,支持超时重试
ipc_msg_deque_t* deque_ptr = nullptr;
do {
auto found_deque_ptr = block.find<ipc_msg_deque_t>(queue_name.c_str());
deque_ptr = found_deque_ptr;
if (deque_ptr) break;
if (timeout.has_value()) {
const auto& elapsed = std::chrono::steady_clock::now() - start_time;
if (elapsed >= timeout.value()) {
throw std::runtime_error("打开IPC队列超时");
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} else {
break;
}
} while (timeout.has_value());
if (!deque_ptr) {
throw std::runtime_error("无法打开IPC队列");
}
deque_ = deque_ptr;
// 查找互斥锁,支持超时重试
boost::interprocess::interprocess_mutex* mutex_ptr = nullptr;
do {
auto found_mutex_ptr = block.find<boost::interprocess::interprocess_mutex>(mutex_name.c_str());
mutex_ptr = found_mutex_ptr;
if (mutex_ptr) break;
if (timeout.has_value()) {
auto elapsed = std::chrono::steady_clock::now() - start_time;
if (elapsed >= timeout.value()) {
throw std::runtime_error("打开IPC队列锁超时");
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} else {
break;
}
} while (timeout.has_value());
if (!mutex_ptr) {
throw std::runtime_error("无法打开IPC队列锁");
}
mutex_ = mutex_ptr;
is_owner_ = false;
}
void destroy() {
shm_mgr::msg_shm().destroy<ipc_msg_deque_t>(get_queue_name());
shm_mgr::msg_shm().destroy<boost::interprocess::interprocess_mutex>(get_mutex_name());
}
bool push(const T& msg) {
boost::interprocess::scoped_lock lock(*mutex_);
try {
deque_->push_back(msg);
return true;
} catch (...) {
return false;
}
}
bool pop(T& msg) {
boost::interprocess::scoped_lock lock(*mutex_);
if (deque_->empty()) {
return false;
}
msg = deque_->front();
deque_->pop_front();
return true;
}
[[nodiscard]] auto empty() const {
boost::interprocess::scoped_lock lock(*mutex_);
return deque_->empty();
}
[[nodiscard]] auto size() const {
boost::interprocess::scoped_lock lock(*mutex_);
return deque_->size();
}
[[nodiscard]] auto get_queue_name() const {
return name_ + "_queue";
}
[[nodiscard]] auto get_mutex_name() const {
return name_ + "_mutex";
}
};

View File

@@ -0,0 +1 @@
#include "plugin_sandbox_data.h"

View File

@@ -0,0 +1,9 @@
#pragma once
#include <boost/atomic.hpp>
class plugin_sandbox_data {
public:
boost::atomic<bool> ready{ false };
boost::atomic<uint64_t> frame_counter{0};
char name[256];
};

View File

@@ -0,0 +1,106 @@
#include "shm_audio_buffer.h"
shm_audio_buffer::shm_audio_buffer(): buffers{ shm_mgr::ab_shm().make_allocator<channel_allocator_t>() } {
}
void shm_audio_buffer::channels(size_t in_ch) {
if (in_ch == 0) {
throw std::invalid_argument("Channel count must be greater than 0");
}
auto alloc = shm_mgr::ab_shm().make_allocator<buffer_allocator_t>();
buffers.clear();
buffers.reserve(in_ch);
for (size_t i = 0; i < in_ch; ++i) {
buffers.emplace_back(alloc);
}
}
void shm_audio_buffer::block_size(size_t in_size) {
if (buffers.empty()) {
throw std::logic_error("Channels must be set before block size");
}
for (auto& buf : buffers) {
buf.resize(in_size, 0.0f); // 初始化为0
}
}
float* shm_audio_buffer::data(size_t ch) {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
return buffers[ch].data();
}
const float* shm_audio_buffer::data(size_t ch) const {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
return buffers[ch].data();
}
bc::vector<float*> shm_audio_buffer::headers() {
bc::vector<float*> ptrs;
ptrs.reserve(buffers.size());
for (auto& buf : buffers) {
ptrs.push_back(buf.data());
}
return ptrs;
}
bc::vector<const float*> shm_audio_buffer::headers() const {
bc::vector<const float*> ptrs;
ptrs.reserve(buffers.size());
for (const auto& buf : buffers) {
ptrs.push_back(buf.data());
}
return ptrs;
}
shm_audio_buffer::channel_vec_t& shm_audio_buffer::block(size_t ch) {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
return buffers[ch];
}
const shm_audio_buffer::channel_vec_t& shm_audio_buffer::block(size_t ch) const {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
return buffers[ch];
}
void shm_audio_buffer::clear() {
for (auto& buf : buffers) {
std::ranges::fill(buf, 0.0f);
}
}
void shm_audio_buffer::copy_from(size_t ch, const float* src, size_t samples) {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
if (samples > buffers[ch].size()) {
throw std::out_of_range("Sample count exceeds buffer size");
}
std::copy_n(src, samples, buffers[ch].begin());
}
void shm_audio_buffer::copy_to(size_t ch, float* dst, size_t samples) const {
if (ch >= buffers.size()) {
throw std::out_of_range("Channel index out of range");
}
if (samples > buffers[ch].size()) {
throw std::out_of_range("Sample count exceeds buffer size");
}
std::copy_n(buffers[ch].begin(), samples, dst);
}

View File

@@ -0,0 +1,71 @@
// shm_audio_buffer.h
#pragma once
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/container/vector.hpp>
#include <memory>
#include <stdexcept>
#include "shm_mgr.h"
namespace bi = boost::interprocess;
namespace bc = boost::container;
class shm_audio_buffer {
public:
using segment_manager = bi::managed_shared_memory::segment_manager;
using mutex_type = bi::interprocess_mutex;
using scoped_lock = bi::scoped_lock<mutex_type>;
using buffer_allocator_t = bi::allocator<float, segment_manager>;
using channel_vec_t = bc::vector<float, buffer_allocator_t>;
using channel_allocator_t = bi::allocator<channel_vec_t, segment_manager>;
using channel_container_t = bc::vector<channel_vec_t, channel_allocator_t>;
// 构造函数显式接收分配器
explicit shm_audio_buffer();
// 设置通道数
void channels(size_t in_ch);
// 获取通道数
[[nodiscard]] size_t channels() const {
return buffers.size();
}
// 设置块大小
void block_size(size_t in_size);
// 获取块大小
[[nodiscard]] size_t block_size() const {
if (buffers.empty()) return 0;
return buffers[0].size();
}
// 获取指定通道的数据指针
[[nodiscard]] float* data(size_t ch);
[[nodiscard]] const float* data(size_t ch) const;
// 获取所有通道的指针数组
[[nodiscard]] bc::vector<float*> headers();
[[nodiscard]] bc::vector<const float*> headers() const;
// 获取指定通道的向量引用
[[nodiscard]] channel_vec_t& block(size_t ch);
[[nodiscard]] const channel_vec_t& block(size_t ch) const;
// 清空所有缓冲区数据
void clear();
// 复制数据到缓冲区
void copy_from(size_t ch, const float* src, size_t samples);
// 复制数据从缓冲区
void copy_to(size_t ch, float* dst, size_t samples) const;
private:
channel_container_t buffers;
};

View File

@@ -0,0 +1,76 @@
/*
* 文件: shm_manager.cpp
* 说明: shm_manager 成员函数实现。
*/
#include "shm_mgr.h"
#include "shm_audio_buffer.h" // AudioBuffer 具体实现
// ---------------------------------------------------------------------------
// 共享内存整体分配
// ---------------------------------------------------------------------------
/**
* @brief 同时创建 / attach 三块 SHM (Message / AudioBuffer / String)
* @return true 创建成功, false 失败 (已打印日志)
*/
bool shm_mgr::allocate_shm() {
try {
message_segment = std::make_shared<expandable_shm_segment>(message_shm_name, message_shm_size, is_engine);
audio_buffer_segment = std::make_shared<expandable_shm_segment>(audio_buffer_shm_name,
audio_buffer_shm_size,
is_engine);
str_segment = std::make_shared<expandable_shm_segment>(string_shm_name, str_shm_size, is_engine);
return true;
}
catch (const bi::interprocess_exception& e) {
spdlog::error("无法创建共享内存:{}", e.what());
return false;
}
}
auto shm_ptr_t::to_local() const -> void* {
void* ptr;
switch (type) {
case shm_block_type::message:
ptr = shm_mgr::msg_shm().get_address_from_handle(handle);
break;
case shm_block_type::audio_buffer:
ptr = shm_mgr::ab_shm().get_address_from_handle(handle);
break;
case shm_block_type::string:
ptr = shm_mgr::str_shm().get_address_from_handle(handle);
break;
default:
throw std::runtime_error("未知的 shm_block_type");
}
if (!ptr) {
throw std::runtime_error("无效的共享内存句柄: " + std::to_string(handle));
}
return ptr;
}
auto shm_ptr_t::from_local(shm_block_type in_type, void* in_ptr) -> shm_ptr_t {
int64_t h = 0;
switch (in_type) {
case shm_block_type::message:
h = shm_mgr::msg_shm().get_handle_from_address(in_ptr);
break;
case shm_block_type::audio_buffer:
h = shm_mgr::ab_shm().get_handle_from_address(in_ptr);
break;
case shm_block_type::string:
h = shm_mgr::str_shm().get_handle_from_address(in_ptr);
break;
default:
throw std::runtime_error("未知的 shm_block_type");
}
if (h == 0) {
throw std::runtime_error("无效的共享内存指针");
}
return shm_ptr_t(in_type, h);
}

View File

@@ -0,0 +1,472 @@
/*
* 文件: shm_manager.h
* 职责: 统一管理项目中所有共享内存段 (Message / AudioBuffer / String)
* 并为外部提供 "构造 / 查找 / 销毁" 等高层工具函数,隐藏 Boost-Interprocess
* 的细节,做到"拿来即用"。
*
* 关键点:
* • 采用 lazy_singleton 保证进程内唯一实例;
* • 引擎端 (is_engine = true) 负责创建并在退出时移除 SHM
* • 插件端 (is_engine = false) 仅负责 attach无权销毁
* • 把 segment_manager 暴露给模板工具 (msg_mgr / ab_mgr)
* 方便外界使用自定义分配器;
* • 自动扩容策略:内存不足时透明创建新块,对外接口保持不变。
*
* ⚠️ 注意:
* • Boost SHM 创建失败时会抛出 interprocess_exception需捕获。
* • 所有模板接口均 inline + static可直接在头文件使用。
*/
#pragma once
// --- 头文件导入 (Headers) ----------------------------------------------------
#include <memory> // std::shared_ptr
#include <string>
#include <utility>
#include <vector>
#include <mutex>
#include <atomic>
#include <boost/atomic/atomic.hpp>
#include <spdlog/spdlog.h>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <utility>
#include "lazy_singleton.h"
#include "rpc/common.h" // allocator_t / shm_string 等依赖
// 前向声明 (避免循环依赖)
class shm_audio_buffer;
// 命名空间别名
namespace bi = boost::interprocess;
using shm_block_ptr = std::shared_ptr<bi::managed_shared_memory>;
// 内部扩容管理器 - 对外完全透明
class expandable_shm_segment {
public:
explicit expandable_shm_segment(std::string base_name,
const std::size_t initial_size,
const bool is_engine) : base_name_(std::move(base_name)),
block_size_(initial_size),
is_engine_(is_engine),
next_block_id_(0) { create_initial_block(); }
~expandable_shm_segment() { if (is_engine_) { cleanup_all_blocks(); } }
// 分配内存 - 自动扩容
void* allocate(std::size_t size) {
std::lock_guard<std::mutex> lock(mutex_);
// 尝试从现有块分配
for (auto& block: blocks_) {
try { return block->allocate(size); }
catch (const bi::bad_alloc&) {
continue; // 这个块满了,尝试下一个
}
}
// 所有块都满了,创建新块
if (is_engine_ && create_new_block()) {
try { return blocks_.back()->allocate(size); }
catch (const bi::bad_alloc&) {
// 新块也分配失败,返回 nullptr
}
}
return nullptr;
}
void deallocate(void* ptr) {
if (!ptr)
return;
std::lock_guard<std::mutex> lock(mutex_);
for (auto& block: blocks_) {
try {
block->deallocate(ptr);
return;
}
catch (...) {
continue; // 指针不属于这个块
}
}
}
template<typename T>
T* construct(const char* name) {
std::lock_guard lock(mutex_);
// 尝试从现有块构造
for (auto& block: blocks_) {
try { return block->construct<T>(name)(); }
catch (const bi::bad_alloc&) { continue; }
}
// 所有块都满了,创建新块
if (is_engine_ && create_new_block()) {
try { return blocks_.back()->construct<T>(name)(); }
catch (const bi::bad_alloc&) {
// 新块也分配失败
}
}
return nullptr;
}
template<typename T, typename... Args>
T* construct(const char* name, Args&&... args) {
std::lock_guard lock(mutex_);
// 尝试从现有块构造
for (const auto& block: blocks_) {
try { return block->construct<T>(name)(std::forward<Args>(args)...); }
catch (const bi::bad_alloc&) { continue; }
}
// 所有块都满了,创建新块
if (is_engine_ && create_new_block()) {
try { return blocks_.back()->construct<T>(name)(std::forward<Args>(args)...); }
catch (const bi::bad_alloc&) {
// 新块也分配失败
}
}
return nullptr;
}
template<typename T>
T* find_or_construct(const char* name) {
std::lock_guard lock(mutex_);
// 首先在所有块中查找
for (const auto& block: blocks_) {
const auto& result = block->find<T>(name);
if (result.first) { return result.first; }
}
// 未找到,尝试构造
for (const auto& block: blocks_) {
try { return block->find_or_construct<T>(name)(); }
catch (const bi::bad_alloc&) { continue; }
}
// 所有块都满了,创建新块
if (is_engine_ && create_new_block()) {
try { return blocks_.back()->find_or_construct<T>(name)(); }
catch (const bi::bad_alloc&) {
// 新块也分配失败
}
}
return nullptr;
}
template<typename T, typename... Args>
T* find_or_construct(const char* name, Args&&... args) {
std::lock_guard lock(mutex_);
// 首先在所有块中查找
for (const auto& block: blocks_) {
const auto& result = block->find<T>(name);
if (result.first) { return result.first; }
}
// 未找到,尝试构造
for (const auto& block: blocks_) {
try { return block->find_or_construct<T>(name)(std::forward<Args>(args)...); }
catch (const bi::bad_alloc&) { continue; }
}
// 所有块都满了,创建新块
if (is_engine_ && create_new_block()) {
try { return blocks_.back()->find_or_construct<T>(name)(std::forward<Args>(args)...); }
catch (const bi::bad_alloc&) {
// 新块也分配失败
}
}
return nullptr;
}
template<typename T>
T* find(const char* name) {
std::lock_guard lock(mutex_);
for (const auto& block: blocks_) {
const auto& result = block->find<T>(name);
if (result.first) { return result.first; }
}
return nullptr;
}
template<typename T>
bool destroy(const char* name) {
std::lock_guard lock(mutex_);
for (auto& block: blocks_) { if (block->destroy<T>(name)) { return true; } }
return false;
}
bi::managed_shared_memory::handle_t get_handle_from_address(const void* ptr) {
std::lock_guard lock(mutex_);
for (auto& block: blocks_) {
try { return block->get_handle_from_address(ptr); }
catch (...) { continue; }
}
return 0; // 无效句柄
}
void* get_address_from_handle(bi::managed_shared_memory::handle_t handle) {
std::lock_guard lock(mutex_);
for (auto& block: blocks_) {
try {
auto ptr = block->get_address_from_handle(handle);
if (ptr)
return ptr;
}
catch (...) { continue; }
}
return nullptr;
}
// 返回第一个块的 segment_manager (保持兼容性)
auto get_segment_manager() {
std::lock_guard lock(mutex_);
return blocks_.empty() ? nullptr : blocks_[0]->get_segment_manager();
}
private:
void create_initial_block() {
try {
const auto& shm_ptr = create_shm_block(base_name_, block_size_);
blocks_.push_back(shm_ptr);
spdlog::debug("创建初始内存块: {} ({}MB)", base_name_, block_size_ / (1024 * 1024));
}
catch (const bi::interprocess_exception& e) { spdlog::error("创建初始内存块失败 {}: {}", base_name_, e.what()); }
}
bool create_new_block() {
if (!is_engine_)
return false;
try {
const auto block_name = base_name_ + "_" + std::to_string(++next_block_id_);
const auto& shm_ptr = create_shm_block(block_name, block_size_);
blocks_.push_back(shm_ptr);
spdlog::info("内存自动扩容: {} ({}MB), 总块数: {}", block_name, block_size_ / (1024 * 1024), blocks_.size());
return true;
}
catch (const bi::interprocess_exception& e) {
spdlog::error("扩容失败 {}: {}", base_name_, e.what());
return false;
}
}
shm_block_ptr create_shm_block(const std::string& name, std::size_t size) {
if (is_engine_) {
bi::shared_memory_object::remove(name.c_str());
spdlog::debug("创建共享内存块: {} ({}MB)", name, size / (1024 * 1024));
return std::make_shared<bi::managed_shared_memory>(bi::create_only, name.c_str(), size);
}
return std::make_shared<bi::managed_shared_memory>(bi::open_only, name.c_str());
}
void cleanup_all_blocks() {
for (std::size_t i = 0; i < blocks_.size(); ++i) {
const auto block_name = (i == 0) ? base_name_ : base_name_ + "_" + std::to_string(i);
bi::shared_memory_object::remove(block_name.c_str());
spdlog::debug("移除共享内存块: {}", block_name);
}
}
std::string base_name_;
std::size_t block_size_;
bool is_engine_;
std::atomic<std::size_t> next_block_id_;
std::vector<shm_block_ptr> blocks_;
mutable std::mutex mutex_;
};
enum class shm_block_type {
invalid = 0,
message,
audio_buffer,
string
};
// 共享内存指针 - 可跨进程传递
struct shm_ptr_t {
shm_block_type type;
int64_t handle;
shm_ptr_t(shm_block_type in_type, int64_t in_handle) : type(in_type), handle(in_handle) {}
[[nodiscard]] auto to_local() const -> void*;
template<typename T>
[[nodiscard]] auto to_local() const -> T* {
return static_cast<T*>(to_local());
}
[[nodiscard]] static auto from_local(shm_block_type in_type, void* in_ptr) -> shm_ptr_t;
[[nodiscard]] static auto null() -> shm_ptr_t { return shm_ptr_t{ shm_block_type::invalid, 0 }; }
[[nodiscard]] bool is_null() const { return type == shm_block_type::invalid || handle == 0; }
explicit operator bool() const { return !is_null(); }
};
template<typename T>
struct shm_obj_t {
shm_ptr_t ptr;
[[nodiscard]] auto is_null() const { return ptr.is_null(); }
[[nodiscard]] auto to_local() const { return ptr.to_local<T>(); }
auto operator ->() const { return ptr.to_local<T>(); }
explicit operator bool() const {
return !ptr.is_null();
}
};
// 包装器 - 保持原有接口完全不变
struct shm_block {
shm_block() = default;
explicit shm_block(shm_block_type in_type, std::shared_ptr<expandable_shm_segment> segment): type(in_type), segment_(std::move(segment)) {
}
shm_block(const shm_block&) = default;
template<typename T>
auto alloc() const { return segment_->allocate(sizeof(T)); }
void dealloc(void* ptr) const { segment_->deallocate(ptr); }
template<typename T>
[[nodiscard]] auto construct() const {
auto ptr = alloc<T>();
if (!ptr)
return shm_ptr_t::null();
new(ptr) T();
return shm_ptr_t::from_local(type, ptr);
}
template<typename T, typename... Args>
[[nodiscard]] auto construct(Args&&... args) const {
auto ptr = alloc<T>();
if (!ptr)
return shm_ptr_t::null();
new(ptr) T(std::forward<Args>(args)...);
return shm_ptr_t::from_local(type, ptr);
}
template<typename T>
[[nodiscard]] auto construct_with_name(const std::string& name) const {
return segment_->construct<T>(name.c_str());
}
template<typename T, typename... Args>
[[nodiscard]] auto construct_with_name(const std::string& name, Args&&... args) const {
return segment_->construct<T>(name.c_str(), std::forward<Args>(args)...);
}
template<typename T>
[[nodiscard]] auto find_or_construct(const std::string& name) const {
return segment_->find_or_construct<T>(name.c_str());
}
template<typename T, typename... Args>
[[nodiscard]] auto find_or_construct(const std::string& name, Args&&... args) const {
return segment_->find_or_construct<T>(name.c_str(), std::forward<Args>(args)...);
}
template<typename T>
[[nodiscard]] auto find(const std::string& name) const { return segment_->find<T>(name.c_str()); }
[[nodiscard]] auto get_handle_from_address(const void* ptr) const { return segment_->get_handle_from_address(ptr); }
[[nodiscard]] auto get_address_from_handle(const int64_t handle) const {
return segment_->get_address_from_handle(handle);
}
template<typename T>
[[nodiscard]] auto get_address_from_handle(const int64_t handle) const {
return static_cast<T*>(get_address_from_handle(handle));
}
template<typename T>
auto destroy(const std::string& name) const { return segment_->destroy<T>(name.c_str()); }
template<typename T>
void destroy(T* ptr) const {
if (ptr) {
ptr->~T();
dealloc(ptr);
}
}
void destroy(const int64_t handle) const {
auto ptr = get_address_from_handle(handle);
if (ptr)
destroy(ptr);
}
[[nodiscard]] auto get_segment_manager() const { return segment_->get_segment_manager(); }
template<typename T>
auto make_allocator() const { return T{ get_segment_manager() }; }
[[nodiscard]] auto get_type() const { return type; }
private:
shm_block_type type;
std::shared_ptr<expandable_shm_segment> segment_;
};
/* -------------------------------------------------------------------------- */
/* shm_manager 类定义 */
/* -------------------------------------------------------------------------- */
class shm_mgr : public lazy_singleton<shm_mgr> {
public:
/* ------------------- 常量 (SHM 名称 & 大小) ------------------- */
const char* message_shm_name = "alicho_message_ipc_memory";
const char* audio_buffer_shm_name = "alicho_audio_buffer_ipc_memory";
const char* string_shm_name = "alicho_string_memory";
const std::size_t message_shm_size = 1024 * 1024 * 256; // 256 MB
const std::size_t audio_buffer_shm_size = 1024 * 1024 * 512; // 512 MB
const std::size_t str_shm_size = 1024 * 1024 * 256; // 256 MB
/* ------------------------- 生命周期 ------------------------- */
/**
* @brief 初始化共享内存环境
* @param in_is_engine true = 引擎进程 (负责创建)false = 插件进程 (只 attach)
*
* 典型用法:
* shm_manager::get_instance().init(is_engine=true);
*/
void init(bool in_is_engine = false) {
is_engine = in_is_engine;
allocate_shm(); // 若失败会记录日志并返回 false
}
~shm_mgr() = default; // 析构由 expandable_shm_segment 自动处理
/* ------------------------ Getter ------------------------ */
[[nodiscard]] static auto msg_shm() { return shm_block{ shm_block_type::message, get_instance().message_segment }; }
[[nodiscard]] static auto ab_shm() { return shm_block{ shm_block_type::audio_buffer, get_instance().audio_buffer_segment }; }
[[nodiscard]] static auto str_shm() { return shm_block{ shm_block_type::string, get_instance().str_segment }; }
private:
/* --------- 内部实现 --------- */
/**
* @brief 分配 / 连接三块共享内存
* @returns true 成功, false 失败 (已记录日志)
*/
bool allocate_shm();
/* --------- 成员变量 --------- */
bool is_engine = false; // 角色标记
std::shared_ptr<expandable_shm_segment> message_segment; // 存放 rpc_message / 队列 等
std::shared_ptr<expandable_shm_segment> audio_buffer_segment; // 存放 AudioBuffer
std::shared_ptr<expandable_shm_segment> str_segment; // 存放 shm_string
};

View File

@@ -0,0 +1,114 @@
#include "shm_string.h"
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <algorithm>
#include <stdexcept>
#include <utility>
#include "shm_mgr.h"
// 构造函数实现
shm_string::shm_string(const std::string& initial_str) { set_string(initial_str); }
shm_string::shm_string(std::string&& initial_str) { set_string(std::move(initial_str)); }
shm_string::~shm_string() {
// 注意由于RPC浅拷贝的存在不能在析构函数中自动销毁
// 共享内存对象需要显式调用destroy()方法
}
// 字符串操作实现
auto shm_string::get_string() const -> std::string {
if (handle == 0) { return {}; }
return with_shm_string([](const shm_string_row* shm_str) -> std::string {
if (shm_str) {
// 直接使用数据指针和长度构造避免c_str()调用
return { shm_str->data(), shm_str->size() };
}
return {};
});
}
auto shm_string::get_string_view() const -> std::string_view {
if (handle == 0) { return {}; }
return with_shm_string([](const shm_string_row* shm_str) -> std::string_view {
if (shm_str) { return { shm_str->data(), shm_str->size() }; }
return {};
});
}
void shm_string::set_string(const std::string& in_str) {
if (handle > 0) { destroy(); }
try {
const auto& block = shm_mgr::str_shm();
// 直接在共享内存中构造字符串
const auto shm_str = block.construct<shm_string_row>(in_str.c_str(), block.make_allocator<char_allocator>());
if (!shm_str) {
handle = 0;
throw std::runtime_error("共享内存字符串构造失败");
}
handle = block.get_handle_from_address(shm_str.to_local());
}
catch (...) {
handle = 0;
throw;
}
}
void shm_string::set_string(std::string&& in_str) {
// 对于移动版本,仍需要拷贝到共享内存,但避免额外的拷贝操作
set_string(in_str); // 编译器会优化移动操作
}
// 状态查询实现
auto shm_string::empty() const noexcept -> bool {
if (handle == 0) { return true; }
try { return with_shm_string([](const shm_string_row* shm_str) -> bool { return !shm_str || shm_str->empty(); }); }
catch (...) {
return true; // 异常情况视为空
}
}
auto shm_string::valid() const noexcept -> bool { return handle > 0; }
auto shm_string::size() const -> size_t {
if (handle == 0) { return 0; }
return with_shm_string([](const shm_string_row* shm_str) -> size_t { return shm_str ? shm_str->size() : 0; });
}
void shm_string::destroy() {
if (handle == 0)
return;
try {
const auto& block = shm_mgr::str_shm();
block.destroy(handle);
}
catch (...) {
// 析构过程中的异常不应该传播
// 可以记录日志但不抛出
}
handle = 0;
}
template<typename Func>
auto shm_string::with_shm_string(Func&& func) const -> decltype(func(nullptr)) {
if (handle == 0) {
return func(nullptr);
}
try {
const auto& block = shm_mgr::str_shm();
const auto found = block.get_address_from_handle<shm_string_row>(handle);
return func(found);
}
catch (...) {
return func(nullptr);
}
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <boost/uuid/uuid.hpp>
#include <string>
#include <string_view>
#include "shm_mgr.h"
#include "rpc/common.h"
using char_allocator = allocator_t<char>;
using shm_string_row = bc::basic_string<char, std::char_traits<char>, char_allocator>;
/**
* 共享内存字符串类 - 支持RPC浅拷贝的跨进程字符串存储
*
* 重要特性:
* - 支持浅拷贝用于RPC传输只拷贝UUID不拷贝实际字符串数据
* - 自动资源管理,但需要注意多个实例可能指向同一共享内存对象
* - 线程安全的共享内存访问
*
* RPC使用注意事项
* - 多个shm_string实例可能共享同一个UUID销毁时需小心
* - 建议在RPC调用后立即使用避免长期持有
*/
class shm_string {
public:
// 默认构造和析构
shm_string() = default;
explicit shm_string(const std::string& initial_str);
explicit shm_string(std::string&& initial_str);
~shm_string();
// 字符串操作接口
[[nodiscard]] auto get_string() const -> std::string;
[[nodiscard]] auto get_string_view() const -> std::string_view;
void set_string(const std::string& in_str);
void set_string(std::string&& in_str);
// 状态查询
[[nodiscard]] auto empty() const noexcept -> bool;
[[nodiscard]] auto valid() const noexcept -> bool;
[[nodiscard]] auto size() const -> size_t;
// 资源管理
void destroy();
void clear() { destroy(); } // 语义化别名
private:
template<typename Func>
auto with_shm_string(Func&& func) const -> decltype(func(nullptr));
int64_t handle;
};

View File

@@ -0,0 +1,12 @@
#pragma once
template<typename T>
class lazy_singleton {
public:
static auto get_instance() -> T& {
static T instance;
return instance;
}
protected:
lazy_singleton() = default;
};

View File

@@ -1,4 +1,4 @@
#include "library_handle.h"
#include "library_handle/library_handle.h"
#include <windows.h>
library_handle* library_handle::create(const std::filesystem::path& in_path) {

View File

@@ -0,0 +1,287 @@
#pragma once
#include <cstdint>
#include <span>
#include <expected>
#include <format>
#include <concepts>
#include <array>
#include <mutex>
#include <shared_mutex>
namespace midi_type {
enum class event_type : std::uint8_t {
note_off = 0x80,
note_on = 0x90,
polyphonic_key_pressure = 0xA0,
control_change = 0xB0,
program_change = 0xC0,
channel_pressure = 0xD0,
pitch_bend = 0xE0,
system_exclusive = 0xF0,
time_code = 0xF1,
song_position = 0xF2,
song_select = 0xF3,
tune_request = 0xF6,
timing_clock = 0xF8,
start = 0xFA,
continue_ = 0xFB,
stop = 0xFC,
active_sensing = 0xFE,
system_reset = 0xFF
};
struct midi_error {
enum class code : std::uint8_t {
invalid_channel,
invalid_velocity,
invalid_note,
invalid_control_number,
invalid_data_byte,
invalid_pitch_bend
};
code code;
std::string_view message;
};
template<typename T>concept midi_data_byte = requires(T value) {
requires std::integral<T>; requires (value >= 0 && value <= 127);
};
template<typename T>concept midi_channel = requires(T channel) {
requires std::integral<T>; requires (channel >= 0 && channel <= 15);
};
class midi_event {
public:
using time_stamp = std::uint64_t;
using data_bytes = std::array<std::uint8_t, 2>;
private:
event_type event_type_;
std::uint8_t channel_;
data_bytes data_;
time_stamp timestamp_;
public:
// C++23 designated initializers support
struct note_on_data {
std::uint8_t note;
std::uint8_t velocity;
};
struct note_off_data {
std::uint8_t note;
std::uint8_t velocity = 0;
};
struct control_change_data {
std::uint8_t controller;
std::uint8_t value;
};
struct pitch_bend_data {
std::uint16_t value; // 14-bit value
};
// Modern constructor with validation
constexpr midi_event(event_type type,
std::uint8_t channel,
data_bytes data = {},
time_stamp timestamp = 0) noexcept : event_type_(type),
channel_(channel & 0x0F),
data_(data),
timestamp_(timestamp) {
}
// Factory methods with compile-time validation
[[nodiscard]] static constexpr auto create_note_on(std::uint8_t channel,
std::uint8_t note,
std::uint8_t velocity,
time_stamp timestamp = 0) noexcept -> std::expected<
midi_event, midi_error> {
if (channel > 15) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
};
}
if (note > 127) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_note, .message = "Note must be 0-127" }
};
}
if (velocity > 127) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_velocity, .message = "Velocity must be 0-127" }
};
}
return midi_event{ event_type::note_on, channel, { note, velocity }, timestamp };
}
[[nodiscard]] static constexpr auto create_note_off(std::uint8_t channel,
std::uint8_t note,
std::uint8_t velocity = 0,
time_stamp timestamp = 0) noexcept -> std::expected<
midi_event, midi_error> {
if (channel > 15) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
};
}
if (note > 127) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_note, .message = "Note must be 0-127" }
};
}
if (velocity > 127) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_velocity, .message = "Velocity must be 0-127" }
};
}
return midi_event{ event_type::note_off, channel, { note, velocity }, timestamp };
}
[[nodiscard]] static constexpr auto create_control_change(std::uint8_t channel,
std::uint8_t controller,
std::uint8_t value,
time_stamp timestamp = 0) noexcept -> std::expected<
midi_event, midi_error> {
if (channel > 15) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
};
}
if (controller > 127) [[unlikely]] {
return std::unexpected{
midi_error{
.code = midi_error::code::invalid_control_number,
.message = "Controller must be 0-127"
}
};
}
if (value > 127) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_data_byte, .message = "Value must be 0-127" }
};
}
return midi_event{ event_type::control_change, channel, { controller, value }, timestamp };
}
[[nodiscard]] static constexpr auto create_pitch_bend(std::uint8_t channel,
std::uint16_t value,
time_stamp timestamp = 0) noexcept -> std::expected<
midi_event, midi_error> {
if (channel > 15) [[unlikely]] {
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
};
}
if (value > 16383) [[unlikely]] { // 14-bit maximum
return std::unexpected{
midi_error{ .code = midi_error::code::invalid_pitch_bend, .message = "Pitch bend must be 0-16383" }
};
}
std::uint8_t lsb = value & 0x7F;
std::uint8_t msb = (value >> 7) & 0x7F;
return midi_event{ event_type::pitch_bend, channel, { lsb, msb }, timestamp };
}
// Modern accessors
[[nodiscard]] constexpr auto type() const noexcept { return event_type_; }
[[nodiscard]] constexpr auto channel() const noexcept { return channel_; }
[[nodiscard]] constexpr auto timestamp() const noexcept { return timestamp_; }
[[nodiscard]] constexpr auto data() const noexcept { return std::span{ data_ }; }
// Specific data accessors
[[nodiscard]] constexpr std::uint8_t note() const noexcept requires(true) { return data_[0]; }
[[nodiscard]] constexpr std::uint8_t velocity() const noexcept { return data_[1]; }
[[nodiscard]] constexpr std::uint8_t controller() const noexcept { return data_[0]; }
[[nodiscard]] constexpr std::uint8_t control_value() const noexcept { return data_[1]; }
[[nodiscard]] constexpr std::uint16_t pitch_bend_value() const noexcept { return data_[0] | (data_[1] << 7); }
// Serialize to raw MIDI bytes
[[nodiscard]] constexpr auto to_bytes() const noexcept -> std::array<std::uint8_t, 3> {
std::uint8_t status = static_cast<std::uint8_t>(event_type_) | channel_;
return { status, data_[0], data_[1] };
}
// Modern comparison operators (C++20 spaceship)
[[nodiscard]] constexpr auto operator<=>(const midi_event& other) const noexcept = default;
[[nodiscard]] constexpr bool operator==(const midi_event& other) const noexcept = default;
// Format support
[[nodiscard]] std::string to_string() const {
return std::format("MidiEvent{{type: 0x{:02X}, channel: {}, data: [{}, {}], timestamp: {}}}",
static_cast<std::uint8_t>(event_type_),
channel_,
data_[0],
data_[1],
timestamp_);
}
};
// Modern RAII MIDI Event Buffer
class midi_event_buffer {
private:
std::vector<midi_event> events_;
mutable std::shared_mutex mutex_;
public:
void add_event(const midi_event& event) {
std::unique_lock lock{ mutex_ };
events_.emplace_back(event);
}
void add_event(midi_event&& event) {
std::unique_lock lock{ mutex_ };
events_.emplace_back(std::move(event));
}
[[nodiscard]] auto get_events() const -> std::vector<midi_event> {
std::shared_lock lock{ mutex_ };
return events_;
}
[[nodiscard]] std::size_t size() const noexcept {
std::shared_lock lock{ mutex_ };
return events_.size();
}
void clear() noexcept {
std::unique_lock lock{ mutex_ };
events_.clear();
}
// Range-based iteration support
[[nodiscard]] auto begin() const {
std::shared_lock lock{ mutex_ };
return events_.begin();
}
[[nodiscard]] auto end() const {
std::shared_lock lock{ mutex_ };
return events_.end();
}
};
} // namespace Midi
// Custom formatter for std::format
template<>
struct std::formatter<midi_type::midi_event> {
static constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
static auto format(const midi_type::midi_event& event, std::format_context& ctx) {
return std::format_to(ctx.out(), "{}", event.to_string());
}
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include <chrono>
using heartbeat_t = std::chrono::time_point<std::chrono::steady_clock>::duration::rep;
using heartbeat_duration_t = std::chrono::steady_clock::duration;

View File

@@ -0,0 +1,45 @@
#pragma once
#include <string>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/container/string.hpp>
namespace bi = boost::interprocess;
namespace bc = boost::container;
constexpr size_t AUDIO_BLOCK_SIZE = 4096; // 最大音频块大小
constexpr size_t QUEUE_CAPACITY = 4; // 音频队列容量
constexpr size_t MAX_PAYLOAD_SIZE = 1024; // 最大有效载荷大小
constexpr size_t SHM_SIZE = 1024 * 1024; // 共享内存段大小
struct audio_block {
float data[AUDIO_BLOCK_SIZE * 2]; // 音频数据, 双声道
size_t size; // 有效数据大小
};
// 单生产者单消费者无锁队列
using lock_free_queue = boost::lockfree::spsc_queue<audio_block, boost::lockfree::capacity<QUEUE_CAPACITY>>;
template<typename T>
using allocator_t = bi::allocator<T, bi::managed_shared_memory::segment_manager>;
template<typename Msg>
struct msg_id_t{};
// 根据 plugin_id 生成 IPC 资源名称
inline std::string get_uds_path() {
#if ALICHO_PLATFORM_WINDOWS
return "alicho_host.sock";
#else
return "/tmp/alicho_host"; // Linux 使用 Unix 域套接字
#endif
}
inline std::string get_shm_in_name(const uint32_t plugin_id) {
return "alicho_host_in_" + std::to_string(plugin_id);
}
inline std::string get_shm_out_name(const uint32_t plugin_id) {
return "alicho_host_out_" + std::to_string(plugin_id);
}

View File

@@ -0,0 +1,59 @@
/*
* 文件: rpc_manager.cpp
* 说明: rpc_message_handler 成员函数实现。
*/
#include "rpc_manager.h"
#include <spdlog/spdlog.h>
// --- RPC 回调注册实现 (Registration) -----------------------------------------
/**
* @brief 为指定的 RPC 消息类型注册/覆盖处理器。
*
* @param type 消息类型
* @param handler 对应处理器
*
* @note
* • 若 type 已存在,则输出 warn 日志并覆盖旧回调(常见于热更新或重复包含)。
*/
void rpc_message_handler::register_rpc_handler(rpc::message_type type, rpc_callback handler) {
if (handlers_.contains(type)) {
spdlog::warn("RPC消息类型 {} 已经注册,覆盖旧处理器", static_cast<uint32_t>(type));
}
handlers_[type] = std::move(handler); // 覆盖 or 新增
}
void rpc_message_handler::register_rpc_destructor(rpc::message_type type, rpc_destructor destructor) {
if (destructors_.contains(type)) {
spdlog::warn("RPC消息类型 {} 的析构器已注册,覆盖旧析构器", static_cast<uint32_t>(type));
}
destructors_[type] = std::move(destructor); // 覆盖 or 新增
}
// --- RPC 消息分发实现 (Dispatch) ---------------------------------------------
/**
* @brief 根据 message_id 派发消息到对应处理器
*
* @param in_msg 收到的 RPC 消息
*
* 流程:
* 1. 在内部 handlers_ 查找对应回调
* 2. 找到则调用,未找到则输出 warn
*/
void rpc_message_handler::handle_message(const rpc_message_t& in_msg) {
if (const auto it = handlers_.find(in_msg.message_id); it != handlers_.end()) {
#if ALICHO_DEBUG
// 条件编译: 调试模式下打印更详细的日志
spdlog::info("处理RPC消息ID: {}", static_cast<uint32_t>(in_msg.message_id));
#endif
// ⚠ 重要: in_msg.data.data() 为 void* 指向 payload具体类型由注册器保证
it->second(in_msg.get_raw_data());
}
else { spdlog::warn("未处理的RPC消息类型: {}", static_cast<uint32_t>(in_msg.message_id)); }
if (const auto dit = destructors_.find(in_msg.message_id); dit != destructors_.end()) {
dit->second(in_msg.get_raw_data());
}
}

View File

@@ -0,0 +1,176 @@
/*
* 文件: rpc_manager.h
* 描述: 为进程间/线程间 RPC 消息提供统一的注册与分发机制。
* 通过 lazy_singleton 保证全局唯一实例,利用无锁共享内存
* vectorboost::container::vector保存消息负载避免不必要的拷贝。
*
* 依赖:
* • spdlog —— 轻量级日志库
* • boost::container::vector —— 支持自定义分配器的 STL-like 容器
* • lazy_singleton —— 项目内简单线程安全单例封装
* • shm_manager —— 管理跨进程共享内存 + 自定义分配器
*
* 关键概念:
* • rpc_message_t —— 所有 RPC 消息统一封装结构
* • rpc_message_handler —— RPC 处理器核心,支持注册 / 分发
* • rpc_handler_register —— 基于模板的静态注册工具,结合宏 RPC_REG 使用
*
* ⚠️ 注意:
* 1. 本文件使用了 unordered_map 但未显式包含 <unordered_map>。
* 为了自给自足,已在下方 includes 中补充。
* 2. 由于模板 + 宏的组合特性,静态注册必须在所有其他源文件静态初始化
* 之前完成,否则可能出现“未注册”情况。
*/
#pragma once
// --- 头文件导入 (Headers) ----------------------------------------------------
#include <unordered_map> // 用于存放 <message_type, callback>
#include <functional> // std::function
#include <cstddef> // std::byte
#include <boost/container/vector.hpp>
#include "lazy_singleton.h"
#include "rpc_type.h"
#include "common.h"
#include "ipc/shm_mgr.h"
namespace bc = boost::container;
// --- 类型别名与前向声明 (Type Aliases & FWD Decls) --------------------------
using rpc_callback = std::function<void(const void*)>; // 统一 RPC 回调签名
using rpc_destructor = std::function<void(const void*)>; // 统一析构回调签名
// --- 数据结构定义 (Structs) --------------------------------------------------
/**
* @struct rpc_message_t
* @brief RPC 消息载体, 将消息 ID 与二进制负载包装为一个整体。
*
* 设计思路:
* • 使用 boost::container::vector 搭配 shm_manager 提供的自定义分配器,
* 直接把 payload 放到共享内存,以降低跨进程数据拷贝成本。
* • 默认构造时 message_id 置为 WTF一个非法占位值防止误用。
*/
struct rpc_message_t {
template<typename T>
void make_data() {
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
const auto& block = shm_mgr::msg_shm();
auto ptr = block.construct<T>();
handle = block.get_handle_from_address(ptr);
}
template<typename T, typename... Args>
void make_data(Args&&... args) {
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
const auto& block = shm_mgr::msg_shm();
auto ptr = block.construct<T>(std::forward<Args>(args)...);
handle = block.get_handle_from_address(ptr);
}
template<typename T>
auto get_data() const -> T* {
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
const auto& block = shm_mgr::msg_shm();
return block.get_address_from_handle<T>(handle);
}
auto get_raw_data() const -> void* {
return shm_mgr::get_instance().msg_shm().get_address_from_handle(handle);
}
rpc::message_type message_id; // 消息类型 ID
int64_t handle; // 共享内存句柄 (跨进程时使用)
};
// --- 核心业务类 (Core Business Class) ---------------------------------------
/**
* @class rpc_message_handler
* @brief 全局唯一的 RPC 分发器:
* 1. 提供 register_rpc_handler() 进行回调注册
* 2. 提供 handle_message() 统一派发入口
*
* 继承 lazy_singleton 保证线程安全 + 懒加载单例。
*/
class rpc_message_handler : public lazy_singleton<rpc_message_handler> {
public:
/**
* @brief 注册新的 RPC 处理回调
*
* @param type 要注册的消息类型
* @param handler 对应的处理器函数
*
* @example
* rpc_message_handler::get_instance().register_rpc_handler(
* rpc::message_type::Ping,
* [](const void* p){ static_cast<const PingMsg*>(p)->process(); }
* );
*/
void register_rpc_handler(rpc::message_type type, rpc_callback handler);
void register_rpc_destructor(rpc::message_type type, rpc_destructor destructor);
/**
* @brief 根据 message_id 分发 RPC 消息
*
* @param in_msg 收到的完整消息结构体
*
* @example
* rpc_message_t msg = receive_from_queue(...);
* rpc_message_handler::get_instance().handle_message(msg);
*/
void handle_message(const rpc_message_t& in_msg);
private:
// 储存 <message_type, 处理回调>
std::unordered_map<rpc::message_type, rpc_callback> handlers_;
// 存储 <message_type, 析构回调>
std::unordered_map<rpc::message_type, rpc_destructor> destructors_;
};
// --- 模板工具 & 宏 (Template Helpers & Macros) -------------------------------
/**
* @template Msg
* @struct rpc_handler_register
* @brief 静态注册器。构造时即完成回调注册,用于零侵入式绑定。
*
* 用法:
* struct Pong { void process() {} };
* inline static auto reg = rpc_handler_register<Pong>(rpc::message_type::Pong);
*/
template<typename Msg>
struct rpc_handler_register {
explicit rpc_handler_register(rpc::message_type in_type) {
static_assert(requires { Msg::rpc_id; }, "Msg 必须具有静态成员 'rpc_id', 是否定义了DEFINE_ID宏?");
static_assert(requires { std::declval<Msg>().process(); }, "Msg 必须具有成员函数 'process()'。");
// 封装真正的回调,实现与 Msg::process() 解耦
auto real_func = [](const void* payload) {
((Msg*)payload)->process();
};
rpc_message_handler::get_instance().register_rpc_handler(in_type, real_func);
// 如果Msg有destroy成员函数, 则注册析构器
if constexpr (requires { std::declval<Msg>().destroy(); }) {
auto real_destructor = [](const void* payload) {
((Msg*)payload)->destroy();
};
rpc_message_handler::get_instance().register_rpc_destructor(in_type, real_destructor);
}
}
};
/**
* @brief 语法糖宏,配合静态变量实现“一行注册”。
* @code
* struct Foo { void process(); };
* RPC_REG(FooType, Foo)
*
* 会展开为:
* inline static auto Foo_register = rpc_handler_register<Foo>(rpc::message_type::FooType);
* @endcode
*/
#define RPC_REG(type, t) \
inline static auto t##_register = rpc_handler_register<t>(rpc::message_type::type);

View File

@@ -0,0 +1,71 @@
// RPC调用消息体结构, 由AudioEngine和PluginHost之间传输
#pragma once
#include <boost/container/vector.hpp>
#include <boost/uuid.hpp>
#include <spdlog/spdlog.h>
#include "ipc/shm_mgr.h"
#include "common.h"
#include "ipc/shm_string.h"
namespace bc = boost::container;
#define DEFINE_ID(type) static constexpr auto rpc_id = rpc::message_type::##type;
namespace rpc {
enum class message_type : uint32_t {
WTF = 0, // 未知消息类型, 用于调试
// Host -> AudioEngine
LOG = 1, // 日志消息
REGISTER_HOST, // 注册插件
PROCESS_DONE, // 处理完成
PARAMETER_VALUE_CHANGED, // 参数值改变
// AudioEngine -> Host
PROCESS_AUDIO_BLOCK = 10001, // 处理音频块
SET_PARAMETER, // 设置参数
SHUTDOWN // 关闭插件
};
}
// Host -> AudioEngine
namespace engine_rpc {
struct log {
DEFINE_ID(LOG)
void set_str(const std::string& in_str) {
str_.set_string(in_str);
}
void set_level(spdlog::level::level_enum in_level) {
level_ = in_level;
}
// destroy函数由rpc_manager自动调用
void destroy() {
str_.destroy();
}
protected:
shm_string str_;
spdlog::level::level_enum level_ = spdlog::level::info;
};
}
// AudioEngine -> Host
namespace host_rpc {
struct load_plugin {
// DEFINE_ID(REGISTER_HOST)
// void set_plugin_path(const std::string& in_path) {
// plugin_path_.set_string(in_path);
// }
// void set_plugin_id(const boost::uuids::uuid& in_id) {
// plugin_id_ = in_id;
// }
//
// // destroy函数由rpc_manager自动调用
// void destroy() {
// plugin_path_.destroy();
// }
};
}

View File

@@ -15,10 +15,6 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(${PROJECT_NAME} ${SRC_FILES})
target_link_libraries(${PROJECT_NAME} PRIVATE
config_target
alicho_proto
gRPC::grpc++
protobuf::libprotobuf
libzmq
glfw
AlichoMisc
)

View File

@@ -1,32 +1,98 @@
#include "GLFW/glfw3.h"
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include "rpc/common.h"
#include "vst2host.h"
#include "spdlog/spdlog.h"
#include <boost/interprocess/managed_shared_memory.hpp>
#include "ipc/ipc_node.h"
namespace bi = boost::interprocess;
int main(int argc, char *argv[])
{
if (argc != 2)
return 1;
shm_mgr::get_instance().init();
plugin_ipc_node node(argv[1]);
float data_counter = 0;
while (true) {
if (!node.is_engine_running())
break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
node.process_rpc();
// 获取一个音频块
auto pending_block = node.get_audio_rb().get_pending_block();
if (pending_block.data) {
// 将待处理的数据写入插件输出
for (size_t i = 0; i < pending_block.size; ++i) {
pending_block.data[i] = data_counter;
data_counter += 0.01f;
if (data_counter > 1.0f)
data_counter = -1.0f;
}
node.get_audio_rb().complete_pending_block();
}
}
#if 0
std::string shm_input_path = argv[1];
std::string shm_output_path = argv[2];
boost::system::error_code ec;
boost::asio::io_context io_context;
boost::asio::local::stream_protocol::socket socket(io_context);
socket.connect(get_uds_path(), ec);
if (ec) {
spdlog::error("无法连接到插件主机: {}", ec.message());
return 1;
}
// 连接共享内存
bi::managed_shared_memory input_segment(bi::open_only, shm_input_path.c_str());
bi::managed_shared_memory output_segment(bi::open_only, shm_output_path.c_str());
// 获取锁无阻塞队列
auto input_queue = input_segment.find<lock_free_queue>("input_queue").first;
auto output_queue = output_segment.find<lock_free_queue>("output_queue").first;
if (!input_queue || !output_queue) {
spdlog::error("无法找到共享内存队列");
return 1;
}
glfwSetErrorCallback([](int error, const char* description) {
std::println(std::cerr, "GLFW Error {}: {}", error, description);
spdlog::error("GLFW 错误 {}: {}", error, description);
});
glfwInit(); // 初始化 GLFW
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // 仅使用 GLFW 的窗口功能,不使用 OpenGL
auto plugin_path = argv[1];
load_plugin(plugin_path, 44100, 512);
open_editor();
while (true) {
if (is_editor_open()) {
glfwPollEvents();
idle_editor();
}
else {
break;
// if (is_editor_open()) {
// glfwPollEvents();
// idle_editor();
// }
// else {
// break;
// }
// 处理音频块
audio_block block;
if (input_queue->pop(block)) {
break; // 这里临时测试, 直接退出
}
constexpr std::chrono::nanoseconds delta_time(std::nano::den / 320); // 240fps刷新率但是留有额外的抖动余量(防止CPU时间片分配不均)
std::this_thread::sleep_for(delta_time);
}
unload_plugin();
glfwTerminate();
bi::shared_memory_object::remove(shm_input_path.c_str());
bi::shared_memory_object::remove(shm_output_path.c_str());
#endif
return 0;
}

View File

@@ -0,0 +1 @@
#include "rpc.h"

View File

@@ -0,0 +1,5 @@
#pragma once
namespace vst2_rpc {
};

View File

@@ -61,7 +61,7 @@ inline VstIntPtr VSTCALLBACK host_callback(AEffect* effect,
}
}
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int in_block_size) {
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int32_t in_block_size) {
lib_ = library_handle::create(in_lib_path);
auto main_entry = lib_->get_func<vst_plugin_entry>("VSTPluginMain");
if (!main_entry) {

View File

@@ -2,21 +2,23 @@
#include "pluginterfaces/vst2.x/aeffectx.h" // Steinberg 官方头
#include <span>
#include "library_handle.h"
#include "library_handle/library_handle.h"
#include <GLFW/glfw3.h>
#include "size_type.h"
#include "audio_ring_buffer.h"
#include "vec.h"
inline library_handle* lib_ = nullptr;
inline AEffect* effect_ = nullptr;
inline float current_sample_rate;
inline int32_t current_block_size;
inline uint32_t plugin_id = 0; // 插件 ID
inline double current_bpm = 120.0; // 默认 120 BPM
inline GLFWwindow* window_handle = nullptr;
inline std::optional<i_vec2> editor_pos{}; // 编辑器最后位置
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int in_block_size);
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int32_t in_block_size);
void unload_plugin();
/* -------- 调度分发 -------- */
@@ -45,7 +47,7 @@ inline void set_sample_rate(float in_sample_rate) {
current_sample_rate = in_sample_rate;
dispatch(effSetSampleRate, 0, 0, nullptr, in_sample_rate);
}
inline void set_block_size(int in_block_size) {
inline void set_block_size(int32_t in_block_size) {
current_block_size = in_block_size;
dispatch(effSetBlockSize, 0, in_block_size);
}
@@ -119,6 +121,11 @@ inline void idle_editor() {
dispatch(effEditIdle);
}
}
/// @brief 获取插件的 chunk 数据
/// @param index 0 → Bankfxb包含插件的全部 program/parameter 状态
/// 1 → Programfxp仅包含当前 program 的状态
/// @return chunk数据
inline std::vector<uint8_t> get_chunk(int index) {
std::vector<uint8_t> chunk;
const auto size = dispatch(effGetChunk, index, 0, nullptr);
@@ -128,6 +135,10 @@ inline std::vector<uint8_t> get_chunk(int index) {
}
return chunk;
}
/// @brief 设置插件的 chunk 数据
/// @param chunk 要设置的 chunk 数据
/// @param index 0为bank, 1为program
inline void set_chunk(const std::span<uint8_t>& chunk, int index) {
if (chunk.empty())
return;

View File

@@ -13,7 +13,7 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(${PROJECT_NAME} ${SRC_FILES})
target_link_libraries(${PROJECT_NAME} PRIVATE
config_target
alicho_proto
AlichoProto
gRPC::grpc++
protobuf::libprotobuf
libzmq

View File

@@ -1,11 +1,30 @@
{
"dependencies": [
"grpc",
"protobuf",
"cppzmq",
"glfw3",
"gtest"
],
"version": "0.0.1",
"name": "ninaengine"
"name" : "alicho",
"version" : "0.0.1",
"builtin-baseline" : "f33cc491c85a7d643c5ab6da1667c1458e6d7abf",
"dependencies" : [ "grpc", "protobuf", "cppzmq", "glfw3", "spdlog", "gtest", {
"name" : "boost-asio",
"version>=" : "1.88.0"
}, {
"name" : "boost-process",
"version>=" : "1.88.0"
}, {
"name" : "boost-interprocess",
"version>=" : "1.88.0"
}, {
"name" : "boost-lockfree",
"version>=" : "1.88.0"
}, {
"name" : "tbb",
"version>=" : "2022.1.0"
}, {
"name" : "boost-circular-buffer",
"version>=" : "1.88.0"
}, {
"name" : "boost-uuid",
"version>=" : "1.88.0"
}, {
"name" : "boost-thread",
"version>=" : "1.88.0"
} ]
}

View File

@@ -1,7 +1,20 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Daw.Project;
using Grpc.Core;
namespace frontend.Views;
class DawProjectSync : Sync.SyncBase
{
public override Task Stream(IAsyncStreamReader<UpdateWrapper> requestStream, IServerStreamWriter<UpdateWrapper> responseStream, ServerCallContext context)
{
// responseStream.WriteAsync();
// requestStream.Current.YUpdate;
return base.Stream(requestStream, responseStream, context);
}
}
public partial class MainWindow : Window
{
public MainWindow()

View File

@@ -36,9 +36,14 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NetMQ" Version="4.0.2.1" />
<PackageReference Include="YDotNet" Version="0.4.3" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\proto\*.proto" GrpcServices="Client" />
<Protobuf Include="..\proto\**\*.proto"
GrpcServices="Both"
Link="Protos\%(RecursiveDir)%(Filename)%(Extension)"
ProtoRoot="..\proto"
/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
syntax = "proto3";
package daw.common;
enum SampleFormat { F32 = 0; F64 = 1; I16 = 2; }
message PluginId { uint32 value = 1; }
message ParameterId { uint32 value = 1; }
message ParameterChange {
PluginId plugin_id = 1;
ParameterId parameter_id = 2;
float value = 3;
uint64 sample_offset = 4; // 相对当前 block
}
message MidiEvent {
PluginId plugin_id = 1;
bytes raw_midi = 2; // 1~3 字节
uint64 sample_offset = 3;
}
message AudioBufferRef {
PluginId plugin_id = 1;
uint64 shm_id = 2;
uint32 offset = 3;
uint32 frames = 4;
SampleFormat format = 5;
uint32 channels = 6;
}

View File

@@ -2,19 +2,21 @@ syntax = "proto3";
package daw.api;
import "google/protobuf/empty.proto";
// ===================================================================
// Service Definitions (gRPC Control Plane)
// ===================================================================
service TransportService {
rpc Play(Empty) returns (StatusResponse);
rpc Pause(Empty) returns (StatusResponse);
rpc Stop(Empty) returns (StatusResponse);
rpc Play(google.protobuf.Empty) returns (StatusResponse);
rpc Pause(google.protobuf.Empty) returns (StatusResponse);
rpc Stop(google.protobuf.Empty) returns (StatusResponse);
rpc SetTempo(SetTempoRequest) returns (StatusResponse);
}
service ProjectService {
rpc NewProject(Empty) returns (ProjectState);
rpc NewProject(google.protobuf.Empty) returns (ProjectState);
rpc LoadProject(LoadProjectRequest) returns (ProjectState);
rpc SaveProject(SaveProjectRequest) returns (StatusResponse);
}
@@ -35,8 +37,6 @@ service PluginService {
// Message Definitions
// ===================================================================
message Empty {}
message StatusResponse {
bool success = 1;
string error_message = 2;

View File

@@ -0,0 +1,84 @@
syntax = "proto3";
package daw.project;
import "google/protobuf/timestamp.proto";
// DAW工程数据同步服务
/* ---- 业务层 ---- */
message MidiNote {
uint32 id = 1;
uint32 pitch = 2; // MIDI note number, e.g., 60 for Middle C
uint32 start = 3; // midi tick
uint32 length = 4; // midi tick
uint32 velocity = 5; // 0-127
}
message MidiClip {
bytes plugin_uuid = 1; // 所属插件的uuid
string name = 2;
repeated MidiNote notes = 3;
}
message AutomationClipPoint {
uint32 id = 1; // 自动化控制点的唯一ID
uint32 tick = 2; // 相对于Clip中的位置 midi tick
float value = 3;
}
message AutomationClip {
bytes plugin_instance_id = 1; // 所属插件实例ID
string name = 2;
uint32 param_id = 3;
repeated AutomationClipPoint points = 4;
}
message Clip {
bytes uuid = 1; // 128bit UUID
string name = 2;
repeated MidiClip midi_clip = 3;
repeated AutomationClip automation_clips = 4;
}
message ClipInstance {
bytes uuid = 1;
uint64 clip_id = 2; // 引用Clip的ID
double clip_start = 3; // 相对于clip的起点 midi tick
double clip_end = 4; // 相对于clip的终点 midi tick
double instance_start = 5; // 全局位置 midi tick
double instance_end = 6; // 全局位置 midi tick
}
message Track {
bytes uuid = 1;
string name = 2;
bool is_muted = 3;
repeated ClipInstance clips = 4;
}
message Plugin {
bytes uuid = 1; // 工程内唯一ID
uint64 plugin_id = 2; // 对应音频插件的ID
string name = 3;
bool is_bypassed = 4;
bytes preset = 5; // 二进制预设数据
}
message Project {
bytes id = 1;
string name = 2;
double bpm = 3;
repeated Clip clips = 4;
repeated Track tracks = 5;
}
/* ---- CRDT 增量封装 ---- */
message UpdateWrapper {
bytes y_update = 1; // yrs/yjs 二进制 diff
uint64 schema_hash = 2; // 快速校验业务 Schema
google.protobuf.Timestamp ts = 3; // 服务端时间戳
}
service Sync {
rpc Stream (stream UpdateWrapper) returns (stream UpdateWrapper);
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package daw.rt;
import "common/domain.proto";
message RTEnvelope {
uint64 seq = 1;
uint64 timestamp_us = 2;
oneof payload {
daw.common.ParameterChange param_change = 10;
daw.common.MidiEvent midi = 11;
daw.common.AudioBufferRef buffer_ref = 12;
}
}