Skip to content

Commit

Permalink
Merge pull request #448 from 4ms/headless-sim
Browse files Browse the repository at this point in the history
Headless simulator
  • Loading branch information
danngreen authored Jan 11, 2025
2 parents e0bf2aa + cffaf42 commit 8a8cfbd
Show file tree
Hide file tree
Showing 25 changed files with 4,021 additions and 8 deletions.
17 changes: 9 additions & 8 deletions firmware/vcv_plugin/internal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
#

set(MM_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../)
set(SHARED ${MM_ROOT}/shared)

add_library(vcv_plugin_internal STATIC
make_element.cc
svg.cc
)

target_include_directories(vcv_plugin_internal PRIVATE
../export
${SHARED}
${MM_ROOT}/firmware/lib
${MM_ROOT}/firmware/src
)
Expand All @@ -21,13 +18,17 @@ target_link_libraries(vcv_plugin_internal PRIVATE
cpputil
metamodule::core-interface
metamodule::rack-interface
lvgl::lvgl
)

set_source_files_properties(svg.cc
PROPERTIES
COMPILE_OPTIONS "-Wno-deprecated-enum-enum-conversion;-Wno-deprecated-anon-enum-enum-conversion;"
)
if (NOT DEFINED METAMODULE_HEADLESS)
target_link_libraries(vcv_plugin_internal PRIVATE lvgl::lvgl)

target_sources(vcv_plugin_internal PRIVATE svg.cc)
set_source_files_properties(svg.cc
PROPERTIES
COMPILE_OPTIONS "-Wno-deprecated-enum-enum-conversion;-Wno-deprecated-anon-enum-enum-conversion;"
)
endif()



146 changes: 146 additions & 0 deletions headless/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
cmake_minimum_required(VERSION 3.14)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

project(MetaModuleHeadless LANGUAGES C CXX)

set(SIMULATOR 1)
set(METAMODULE_HEADLESS 1)

set(FWDIR ${CMAKE_CURRENT_LIST_DIR}/../firmware)
set(LIBDIR ${CMAKE_CURRENT_LIST_DIR}/../firmware/lib)

add_compile_options(
-Ofast
-ffast-math
-funsafe-math-optimizations
)
include(${FWDIR}/cmake/log_levels.cmake)
enable_logging()

# #################### cpputil #########################################

add_subdirectory(${LIBDIR}/cpputil cpputil)
target_compile_definitions(cpputil PUBLIC CPPUTIL_STATIC_STRING_USE_STD_STRING)


# #################### RYML ##################################

add_subdirectory(${LIBDIR}/patch-serial ${CMAKE_CURRENT_BINARY_DIR}/patch-serial)


# #################### VCV Adaptor ##################################

add_subdirectory(${FWDIR}/vcv_plugin ${CMAKE_CURRENT_BINARY_DIR}/vcv_plugin)

# #################### Other brands ##################################

add_subdirectory(${FWDIR}/vcv_ports/ ${CMAKE_CURRENT_BINARY_DIR}/vcv_ports)
target_compile_options(vcv_ports INTERFACE -Wno-double-promotion)

# #################### Application ############################################

add_subdirectory(${LIBDIR}/CoreModules ${CMAKE_CURRENT_BINARY_DIR}/CoreModules)
add_subdirectory(${LIBDIR}/CoreModules/4ms ${CMAKE_CURRENT_BINARY_DIR}/CoreModules-4ms)
target_compile_options(CoreModules INTERFACE -Wno-double-promotion)


add_executable(
headless
src/main.cc
src/audio_files.cc
stubs/svg.cc
stubs/random.cpp

${FWDIR}/coreproc_plugin/create.cc

${FWDIR}/metamodule-plugin-sdk/version.cc
)

target_include_directories(
headless
PRIVATE src
stubs
${FWDIR}/src
${FWDIR}/src/console
${FWDIR}/src/medium
${FWDIR}
.
)

target_compile_options(headless PRIVATE -Wall -Og)
target_compile_options(headless PRIVATE -Wno-double-promotion)

# Workaround for std::expected not available in clang
# https://www.reddit.com/r/cpp/comments/1b6f3s4/any_news_on_when_libc_is_going_to_support/
# https://github.com/llvm/llvm-project/issues/62801

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(headless PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-D__cpp_concepts=202002>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-builtin-macro-redefined>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-macro-redefined>
)
endif()


target_link_libraries(headless PRIVATE
metamodule::patch-serial
vcv_ports
)
target_link_libraries(headless PUBLIC "$<LINK_LIBRARY:WHOLE_ARCHIVE,CoreModules-4ms>")
target_link_libraries(headless PUBLIC "$<LINK_LIBRARY:WHOLE_ARCHIVE,vcv_plugin_internal>")
target_link_libraries(headless PUBLIC "$<LINK_LIBRARY:WHOLE_ARCHIVE,vcv_plugin_export>")

target_compile_definitions(headless PUBLIC SIMULATOR)

set_property(TARGET headless PROPERTY CXX_STANDARD 23)

# Arch-dependent Link options:
message("CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}")
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_link_options(headless PUBLIC "-Wl,-map,headless.map")
else()
target_link_options(headless PUBLIC LINKER:-Map,headless.map)
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_options(headless PUBLIC LINKER:-Map,headless.map)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_link_options(headless PRIVATE "/MAP:headless.map")
endif()


## Debugging
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
find_program(CMAKE_DEBUGGER_BINARY lldb)
set(DEBUG_COMMAND ${CMAKE_DEBUGGER_BINARY} $<TARGET_FILE:headless> --
--sdcarddir ${CMAKE_CURRENT_LIST_DIR}/patches
--flashdir ${CMAKE_CURRENT_LIST_DIR}/../patches/default
--assets ${CMAKE_CURRENT_LIST_DIR}/build/assets.uimg
)


elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_program(CMAKE_DEBUGGER_BINARY gdb)
set(DEBUG_COMMAND ${CMAKE_DEBUGGER_BINARY} -q
-ex \"set debuginfod enabled off\"
-ex \"set pagination off\"
-ex \"run\"
--args $<TARGET_FILE:headless>
--sdcarddir ${CMAKE_CURRENT_LIST_DIR}/patches
--flashdir ${CMAKE_CURRENT_LIST_DIR}/../shared/patch/default
--assets ${CMAKE_CURRENT_LIST_DIR}/build/assets.uimg
)
else ()
set(DEBUG_COMMAND echo "Compiler is ${CMAKE_CXX_COMPILER_ID}, but must be Clang, AppleClang, or GNU to debug")
endif()

add_custom_target(
debug
DEPENDS headless
COMMAND ${DEBUG_COMMAND}
USES_TERMINAL
)

include(ext-plugins.cmake)
target_include_directories(headless PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/ext_plugin)
31 changes: 31 additions & 0 deletions headless/CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "Default",
"displayName": "Normal headless build",
"description": "Sets Ninja generator, build and install directory",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build"
}
},
{
"name": "gcc-warn",
"displayName": "GCC",
"description": "Warnings-enabled build options for GCC",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-Wall -Wpedantic -Werror=return-type -Wsuggest-override -Wuninitialized -Wdouble-promotion"
}
}
]
}
24 changes: 24 additions & 0 deletions headless/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BUILDDIR := build

all: | $(BUILDDIR)
cmake --build $(BUILDDIR)

$(BUILDDIR):
cmake --fresh --preset Default -DLOG_LEVEL=DEBUG

clean:
cmake --fresh --preset Default -DLOG_LEVEL=DEBUG

realclean:
rm -rf $(BUILDDIR)

warnings:
cmake --fresh --preset gcc-warn -DLOG_LEVEL=TRACE

run: all
build/headless

debug:
cmake --build ${BUILDDIR} --target debug

.PHONY: all clean warnings run
44 changes: 44 additions & 0 deletions headless/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## MetaModule Headless

This is designed to run a single patch, reading audio in from a file and saving
the audio output to a file.

**Usage:**
```
cd headless
cmake -B build
cmake --build build
build/headless -p../patches/default/Djembe4Verb.yml
```
The `-p` option specifies the patch file to load.

You can provide it a stereo .wav file with the `--in` option. This will be sent
to In 1 and In 2. If you don't provide a file, then it will send silence to
those channels.

The output from Out 1 and Out 2 is saved to a .wav file, you can specify it
with `--out` or else a default name will be used.

You can specify the number of samples to process with `-n`, for example `-n
48000` will process 1 second of sound at 48kHz.


**Purpose:**
This project is useful for testing the audio stream while developing, for
example, making sure that optimizations do not effect the audio outputs. It
also displays the processing time as a percentage of sample time at 48kHz,
which is useful for seeing if optimizations are having an effect (on the host
computer, at least).

Finally, we've been using this already to compare the MM engine performance on
various processors.

**Future:**

A future improvement would be to tie it into the OS's audio drivers, so it
could stream audio. The driver adaptor should be done such that it can tie into
the hardware MM's audio stream easily (so we can run headless on the MM).

With streaming audio working, it could then respond to MIDI events on the host
computer, so you could use it as a full headless synth.

64 changes: 64 additions & 0 deletions headless/ext-plugins.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Two steps to build an external plugin into the simulator:
#
# 1. Copy/paste the two `list APPEND commands` below, puttting in the path to the
# plugin (where the CMakeLists.txt lives), and the plugin cmake library name
#
# 2. In the plugin, find the file where init(rack::Plugin*) is defined, and rename it to init_Brand
# You can use #if defined(METAMODULE_BUILTIN) so that it's still called init() when building as a plugin for Rack or MM.
# `Brand` in init_Brand must match the plugin cmake library name you used in step 1.

# Example with Venom:
# list(APPEND ext_builtin_brand_paths "${CMAKE_CURRENT_LIST_DIR}/../../metamodule-plugin-examples/Venom")
# list(APPEND ext_builtin_brand_libname "Venom")


#
# Asset dir
#

cmake_path(SET ASSET_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets")
set(ASSET_IMG_FILENAME assets.uimg)
cmake_path(APPEND ASSET_IMG_PATH "${CMAKE_CURRENT_BINARY_DIR}" "${ASSET_IMG_FILENAME}")

message("set ASSET_DIR to ${ASSET_DIR}")
message("set ASSET_IMG_PATH to ${ASSET_IMG_PATH}")

add_custom_command(
OUTPUT ${ASSET_DIR}
COMMAND ${CMAKE_COMMAND} -E echo Copying "${FWDIR}/assets" to "${ASSET_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${FWDIR}/assets" "${ASSET_DIR}"
COMMENT "Copying assets/ dir from ${FWDIR}/assets to ${ASSET_DIR}"
VERBATIM USES_TERMINAL
)

add_custom_command(
OUTPUT ${ASSET_IMG_PATH}
COMMAND cd ${ASSET_DIR} && ${CMAKE_COMMAND} -E tar -cf ${ASSET_IMG_PATH}.tar .
COMMAND cd ${FWDIR} && flashing/uimg_header.py --name Assets ${ASSET_IMG_PATH}.tar ${ASSET_IMG_PATH}
COMMENT "Creating assets uimg file at ${ASSET_IMG_PATH}"
DEPENDS ${ASSET_DIR}
VERBATIM USES_TERMINAL
)

add_custom_target(asset-image ALL
DEPENDS ${ASSET_IMG_PATH}
)

set(EXT_PLUGIN_INIT_CALLS "")

foreach(branddir brand IN ZIP_LISTS ext_builtin_brand_paths ext_builtin_brand_libname)
set(METAMODULE_SDK_DIR ${CMAKE_CURRENT_LIST_DIR})
add_subdirectory(${branddir} ${CMAKE_CURRENT_BINARY_DIR}/builtins/${brand})

target_link_libraries(${brand} PRIVATE metamodule::vcv-plugin-interface)
target_link_libraries(${brand} PRIVATE cpputil::cpputil)
target_compile_definitions(${brand} PRIVATE METAMODULE METAMODULE_BUILTIN)

target_link_libraries(_vcv_ports_internal PUBLIC ${brand})
add_dependencies(asset-image ${brand}-assets)

string(APPEND EXT_PLUGIN_INIT_CALLS "\textern void init_${brand}(rack::plugin::Plugin *);\n\tinit_${brand}(&internal_plugins.emplace_back(\"${brand}\"));")
endforeach()

configure_file(src/ext_plugin_builtin.hh.in ${CMAKE_CURRENT_BINARY_DIR}/ext_plugin/ext_plugin_builtin.hh)

41 changes: 41 additions & 0 deletions headless/plugin.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# TODO: Get cmake to copy the source file that contains init() and rename that function to init_BRAND()
# Also see if Cmake can generate the calls to init (currently manually written into simulator/src/ext_plugin_builtin.hh)

# file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/replace.cmake"
# [=[
# file(READ "${SOURCE}" TEXT)
# string(REPLACE "foo" "bar" TEXT "${TEXT}")
# file(WRITE "${TARGET}" "${TEXT}")
# ]=])

# add_custom_command(
# OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/plugin.cpp"
# COMMAND "${CMAKE_COMMAND}"
# "-DSOURCE=${CMAKE_CURRENT_SOURCE_DIR}/plugin.cpp"
# "-DTARGET=${CMAKE_CURRENT_BINARY_DIR}/plugin.cpp"
# -P "${CMAKE_CURRENT_BINARY_DIR}/replace.cmake"
# DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/plugin.cpp" "${CMAKE_CURRENT_BINARY_DIR}/replace.cmake"
# )


function(create_plugin)
message("Building ${brand} as built-in plugin (create_plugin)")

# target_sources(${PLUGIN_OPTIONS_SOURCE_LIB} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/plugin.cpp)

set(oneValueArgs SOURCE_LIB SOURCE_ASSETS DESTINATION PLUGIN_NAME PLUGIN_JSON)
cmake_parse_arguments(PLUGIN_OPTIONS "" "${oneValueArgs}" "" ${ARGN} )

add_custom_command(
OUTPUT "${ASSET_DIR}/${PLUGIN_OPTIONS_PLUGIN_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PLUGIN_OPTIONS_SOURCE_ASSETS}" "${ASSET_DIR}/${PLUGIN_OPTIONS_PLUGIN_NAME}"
COMMENT "Copying ${PLUGIN_OPTIONS_SOURCE_ASSETS} to ${ASSET_DIR}/${PLUGIN_OPTIONS_PLUGIN_NAME}"
VERBATIM
)

add_custom_target(${PLUGIN_OPTIONS_SOURCE_LIB}-assets
DEPENDS "${ASSET_DIR}/${PLUGIN_OPTIONS_PLUGIN_NAME}"
)


endfunction()
Loading

0 comments on commit 8a8cfbd

Please sign in to comment.