Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmake/avendish.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ option(AVND_ENABLE_PD "Enable Pure Data backend" ON)
option(AVND_ENABLE_MAX "Enable Max/MSP backend" ON)
option(AVND_ENABLE_VST3 "Enable VST3 backend" ON)
option(AVND_ENABLE_CLAP "Enable CLAP backend" ON)
option(AVND_ENABLE_CSOUND "Enable Csound opcode backend" ON)
option(AVND_ENABLE_VINTAGE "Enable VST 2.4 backend" ON)
option(AVND_ENABLE_TOUCHDESIGNER "Enable TouchDesigner backend" ON)
option(AVND_ENABLE_PYTHON "Enable Python backend" ON)
Expand Down Expand Up @@ -276,6 +277,7 @@ include(avendish.python)
include(avendish.vintage)
include(avendish.vst3)
include(avendish.clap)
include(avendish.csound)
include(avendish.ossia)
include(avendish.standalone)
include(avendish.touchdesigner)
Expand Down Expand Up @@ -332,6 +334,7 @@ function(avnd_make_audioplug)
_avnd_dispatch_backend(vintage ${ARGV})
_avnd_dispatch_backend(clap ${ARGV})
_avnd_dispatch_backend(vst3 ${ARGV})
_avnd_dispatch_backend(csound ${ARGV})
_avnd_dispatch_backend(wasm ${ARGV})
avnd_make_example_host(${ARGV})
_avnd_dispatch_backend(gstreamer ${ARGV} PROCESSOR_TYPE AUDIO)
Expand Down
119 changes: 119 additions & 0 deletions cmake/avendish.csound.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
find_path(CSOUND_HEADER NAMES csdl.h PATH_SUFFIXES csound)
if(NOT CSOUND_HEADER)
message(STATUS "Csound headers (csdl.h) not found, skipping bindings...")
function(avnd_make_csound)
endfunction()

return()
endif()

# Shared PCH, reused by every per-object external (mirrors Avendish_pd_pch).
add_library(Avendish_csound_pch STATIC "${AVND_SOURCE_DIR}/src/dummy.cpp")

target_include_directories(
Avendish_csound_pch
PRIVATE
${CSOUND_HEADER}
)

target_precompile_headers(Avendish_csound_pch
PUBLIC
include/avnd/binding/csound/all.hpp
include/avnd/prefix.hpp
)

target_link_libraries(Avendish_csound_pch
PUBLIC
DisableExceptions
)

avnd_common_setup("" "Avendish_csound_pch")

function(avnd_make_csound)
cmake_parse_arguments(AVND "" "TARGET;MAIN_FILE;MAIN_CLASS;C_NAME" "" ${ARGN})

string(MAKE_C_IDENTIFIER "${AVND_MAIN_CLASS}" MAIN_OUT_FILE)

configure_file(
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/prototype.cpp.in"
"${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_csound.cpp"
@ONLY
NEWLINE_STYLE LF
)

if(APPLE)
set_source_files_properties("${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_csound.cpp" PROPERTIES COMPILE_FLAGS -Wno-unreachable-code)
endif()

set(AVND_FX_TARGET "${AVND_TARGET}_csound")
add_library(${AVND_FX_TARGET} MODULE)

set_target_properties(
${AVND_FX_TARGET}
PROPERTIES
OUTPUT_NAME "${AVND_C_NAME}"
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY csound
RUNTIME_OUTPUT_DIRECTORY csound
ARCHIVE_OUTPUT_DIRECTORY csound
)

target_sources(
${AVND_FX_TARGET}
PRIVATE
"${AVND_MAIN_FILE}"
"${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_csound.cpp"
)

target_include_directories(
${AVND_FX_TARGET}
PRIVATE
"${CSOUND_HEADER}"
)

target_compile_definitions(
${AVND_FX_TARGET}
PRIVATE
AVND_CSOUND=1
)

if(NOT MSVC)
target_precompile_headers(${AVND_FX_TARGET}
REUSE_FROM
Avendish_csound_pch
)
endif()

target_link_libraries(
${AVND_FX_TARGET}
PUBLIC
DisableExceptions
Avendish::Avendish_csound
)

# Opcode plugins resolve the API through the CSOUND* function table at load
# time, so the engine symbols are deliberately left undefined in the library.
if(APPLE)
target_link_libraries(${AVND_FX_TARGET} PRIVATE -Wl,-undefined,dynamic_lookup)
elseif(UNIX)
target_link_libraries(${AVND_FX_TARGET} PRIVATE -Wl,--allow-shlib-undefined)
endif()

avnd_common_setup("${AVND_TARGET}" "${AVND_FX_TARGET}")
endfunction()

add_library(Avendish_csound INTERFACE)
target_link_libraries(Avendish_csound INTERFACE Avendish)
target_include_directories(Avendish_csound INTERFACE "${CSOUND_HEADER}")
add_library(Avendish::Avendish_csound ALIAS Avendish_csound)

target_sources(Avendish PRIVATE
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/all.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/audio_processor.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/configure.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/helpers.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/inputs.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/message_processor.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/outputs.hpp"
"${AVND_SOURCE_DIR}/include/avnd/binding/csound/typestrings.hpp"
)
7 changes: 7 additions & 0 deletions include/avnd/binding/csound/all.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

/* SPDX-License-Identifier: GPL-3.0-or-later */

#include <avnd/binding/csound/audio_processor.hpp>
#include <avnd/binding/csound/configure.hpp>
#include <avnd/binding/csound/message_processor.hpp>
144 changes: 144 additions & 0 deletions include/avnd/binding/csound/audio_processor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#pragma once

/* SPDX-License-Identifier: GPL-3.0-or-later */

#include <avnd/binding/csound/helpers.hpp>
#include <avnd/binding/csound/inputs.hpp>
#include <avnd/binding/csound/outputs.hpp>
#include <avnd/binding/csound/typestrings.hpp>
#include <avnd/common/span_polyfill.hpp>
#include <avnd/concepts/port.hpp>
#include <avnd/wrappers/controls.hpp>
#include <avnd/wrappers/effect_container.hpp>
#include <avnd/wrappers/metadatas.hpp>
#include <avnd/wrappers/prepare.hpp>
#include <avnd/wrappers/process_adapter.hpp>

#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <new>

namespace csound
{
/**
* Csound opcode wrapping an Avendish audio processor.
*
* Csound allocates `sizeof(audio_opcode)` zero-initialised bytes and fills the
* `args[]` pointers before calling init() (i-time) then perf() (each k-cycle).
* The non-trivial C++ members are placement-new'd over that memory at i-time and
* destroyed through a registered deinit callback (note deactivation / reset).
*/
template <typename T>
struct audio_opcode
{
static constexpr int n_args
= (sig<T>::n_out + sig<T>::n_in) > 0 ? (sig<T>::n_out + sig<T>::n_in) : 1;
static constexpr int in_chans = sig<T>::n_audio_in;
static constexpr int out_chans = sig<T>::n_audio_out;

// ── Csound-managed: opcode header + one pointer per declared argument ──
OPDS h;
MYFLT* args[n_args];

// ── Our private state, constructed at i-time over the engine's memory ──
bool initialized;
avnd::effect_container<T> implementation;
avnd::process_adapter<T> processor;

static int deinit(CSOUND*, void* p) noexcept
{
auto* self = static_cast<audio_opcode*>(p);
if(self->initialized)
{
self->processor.~process_adapter<T>();
self->implementation.~effect_container<T>();
self->initialized = false;
}
return OK;
}

int init(CSOUND* cs)
{
g_csound = cs;

if(!initialized)
{
::new(&implementation) avnd::effect_container<T>{};
::new(&processor) avnd::process_adapter<T>{};
cs->RegisterDeinitCallback(cs, this, &audio_opcode::deinit);
initialized = true;
}

const avnd::process_setup setup{
.input_channels = in_chans,
.output_channels = out_chans,
.frames_per_buffer = (int)h.insdshead->ksmps,
.rate = (double)cs->GetSr(cs)};

processor.allocate_buffers(setup, MYFLT{});
avnd::prepare(implementation, setup);

if constexpr(avnd::has_inputs<T>)
avnd::init_controls(implementation);

implementation.init_channels(in_chans, out_chans);
return OK;
}

int perf(CSOUND* cs)
{
g_csound = cs;

const uint32_t ksmps = h.insdshead->ksmps;
const uint32_t offset = h.insdshead->ksmps_offset;
const uint32_t early = h.insdshead->ksmps_no_end;
const uint32_t nsmps = ksmps - early;

// Pull k-rate / string control inputs into the parameters.
if constexpr(avnd::has_inputs<T>)
inputs<T>::read(implementation, args);

// Csound passes one MYFLT[ksmps] vector per audio channel; advance every
// pointer by `offset` so we only touch the sample-accurate active region.
std::array<MYFLT*, in_chans> in;
for(int c = 0; c < in_chans; ++c)
in[c] = args[sig<T>::audio_in_off + c] + offset;

std::array<MYFLT*, out_chans> out;
for(int c = 0; c < out_chans; ++c)
{
MYFLT* o = args[sig<T>::audio_out_off + c];
// Sample-accuracy: silence the lead-in and tail samples we won't write.
if(offset > 0)
std::fill_n(o, offset, MYFLT{});
if(early > 0)
std::fill_n(o + nsmps, early, MYFLT{});
out[c] = o + offset;
}

processor.process(
implementation, avnd::span<MYFLT*>{in.data(), (std::size_t)in_chans},
avnd::span<MYFLT*>{out.data(), (std::size_t)out_chans},
(int)(nsmps - offset));

// Push control / value outputs.
if constexpr(avnd::has_outputs<T>)
outputs<T>::write(implementation, args);

return OK;
}

static int register_opcode(CSOUND* cs)
{
static constexpr auto name = avnd::get_c_identifier<T>();
return cs->AppendOpcode(
cs, name.data(), (int)sizeof(audio_opcode<T>), 0, sig<T>::thread,
sig<T>::outypes.data(), sig<T>::intypes.data(),
+[](CSOUND* c, void* p) { return static_cast<audio_opcode*>(p)->init(c); },
+[](CSOUND* c, void* p) { return static_cast<audio_opcode*>(p)->perf(c); },
nullptr);
}
};
}
68 changes: 68 additions & 0 deletions include/avnd/binding/csound/configure.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

/* SPDX-License-Identifier: GPL-3.0-or-later */

#include <avnd/binding/csound/helpers.hpp>
#include <halp/log.hpp>

#include <sstream>
#include <utility>

namespace csound
{
/**
* Routes avnd/halp logging to the Csound console (csound->Message / ErrorMsg)
* through the thread-local engine pointer set in helpers.hpp.
*/
struct logger
{
template <typename... T>
static void log(T&&... args)
{
std::ostringstream str;
((str << args), ...);
csound::post(str.str());
}

template <typename... T>
static void error(T&&... args)
{
std::ostringstream str;
((str << args), ...);
csound::bug(str.str());
}

template <typename... T>
static void trace(T&&... args) noexcept
{
logger::log(std::forward<T>(args)...);
}
template <typename... T>
static void debug(T&&... args) noexcept
{
logger::log(std::forward<T>(args)...);
}
template <typename... T>
static void info(T&&... args) noexcept
{
logger::log(std::forward<T>(args)...);
}
template <typename... T>
static void warn(T&&... args) noexcept
{
logger::error(std::forward<T>(args)...);
}
template <typename... T>
static void critical(T&&... args) noexcept
{
logger::error(std::forward<T>(args)...);
}
};

static_assert(avnd::logger<csound::logger>);

struct config
{
using logger_type = csound::logger;
};
}
Loading
Loading