Repository: elk-audio/sushi Branch: master Commit: 08f24e9cd47c Files: 458 Total size: 3.0 MB Directory structure: gitextract_9o1ns6zt/ ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── COPYING ├── HISTORY.md ├── LICENSE.md ├── README.md ├── apps/ │ ├── CMakeLists.txt │ └── main.cpp ├── bitbucket-pipelines.yml ├── docs/ │ └── LIBRARY.md ├── include/ │ ├── sushi/ │ │ ├── compile_time_settings.h │ │ ├── constants.h │ │ ├── control_interface.h │ │ ├── control_notifications.h │ │ ├── coreaudio_devices_dump.h │ │ ├── elk_sentry_log_sink.h │ │ ├── factory_interface.h │ │ ├── offline_factory.h │ │ ├── options.h │ │ ├── parameter_dump.h │ │ ├── portaudio_devices_dump.h │ │ ├── reactive_factory.h │ │ ├── rt_controller.h │ │ ├── sample_buffer.h │ │ ├── standalone_factory.h │ │ ├── sushi.h │ │ ├── sushi_time.h │ │ ├── terminal_utilities.h │ │ ├── types.h │ │ └── utils.h │ └── version.h.in ├── misc/ │ ├── .gitkeep │ ├── README.md │ └── config_files/ │ ├── arp_peakmeter_osc_broadcast.json │ ├── config_play_arp_link_midi_out.json │ ├── cv_lfo.json │ ├── cv_to_cutoff.json │ ├── cv_to_synth.json │ ├── empty.json │ ├── freeverb_aux.json │ ├── fx.json │ ├── multi_track.json │ ├── multichannel_plugin.json │ ├── play_arp_mda_link.json │ ├── play_brickworks_fx.json │ ├── play_brickworks_synth.json │ ├── play_cv_arp.json │ ├── play_lv2.json │ ├── play_lv2_jx10.json │ ├── play_master_gain.json │ ├── play_vst2.json │ ├── play_vst3.json │ ├── play_wav_streamer.json │ ├── prepost_tracks.json │ ├── rt_midi.json │ ├── send_return_seq.json │ └── vst_8_ch.json ├── rpc_interface/ │ ├── CMakeLists.txt │ ├── include/ │ │ └── sushi_rpc/ │ │ └── grpc_server.h │ └── src/ │ ├── async_service_call_data.cpp │ ├── async_service_call_data.h │ ├── control_service.cpp │ ├── control_service.h │ └── grpc_server.cpp ├── src/ │ ├── audio_frontends/ │ │ ├── apple_coreaudio/ │ │ │ ├── apple_coreaudio_device.h │ │ │ ├── apple_coreaudio_device.mm │ │ │ ├── apple_coreaudio_object.cpp │ │ │ ├── apple_coreaudio_object.h │ │ │ ├── apple_coreaudio_system_object.h │ │ │ ├── apple_coreaudio_utils.cpp │ │ │ └── apple_coreaudio_utils.h │ │ ├── apple_coreaudio_frontend.cpp │ │ ├── apple_coreaudio_frontend.h │ │ ├── audio_frontend_internals.h │ │ ├── base_audio_frontend.cpp │ │ ├── base_audio_frontend.h │ │ ├── coreaudio_devices_dump.cpp │ │ ├── jack_frontend.cpp │ │ ├── jack_frontend.h │ │ ├── offline_frontend.cpp │ │ ├── offline_frontend.h │ │ ├── portaudio_devices_dump.cpp │ │ ├── portaudio_frontend.cpp │ │ ├── portaudio_frontend.h │ │ ├── reactive_frontend.cpp │ │ ├── reactive_frontend.h │ │ ├── xenomai_raspa_frontend.cpp │ │ └── xenomai_raspa_frontend.h │ ├── concrete_sushi.cpp │ ├── concrete_sushi.h │ ├── control_frontends/ │ │ ├── alsa_midi_frontend.cpp │ │ ├── alsa_midi_frontend.h │ │ ├── base_control_frontend.cpp │ │ ├── base_control_frontend.h │ │ ├── base_midi_frontend.h │ │ ├── osc_frontend.cpp │ │ ├── osc_frontend.h │ │ ├── osc_utils.h │ │ ├── oscpack_osc_messenger.cpp │ │ ├── oscpack_osc_messenger.h │ │ ├── reactive_midi_frontend.cpp │ │ ├── reactive_midi_frontend.h │ │ ├── rt_midi_frontend.cpp │ │ └── rt_midi_frontend.h │ ├── dsp_library/ │ │ ├── biquad_filter.cpp │ │ ├── biquad_filter.h │ │ ├── envelopes.h │ │ ├── master_limiter.h │ │ ├── sample_wrapper.h │ │ └── value_smoother.h │ ├── engine/ │ │ ├── audio_engine.cpp │ │ ├── audio_engine.h │ │ ├── audio_graph.cpp │ │ ├── audio_graph.h │ │ ├── base_engine.h │ │ ├── base_event_dispatcher.h │ │ ├── base_processor_container.h │ │ ├── connection_storage.h │ │ ├── controller/ │ │ │ ├── audio_graph_controller.cpp │ │ │ ├── audio_graph_controller.h │ │ │ ├── audio_routing_controller.cpp │ │ │ ├── audio_routing_controller.h │ │ │ ├── completion_sender.h │ │ │ ├── controller.cpp │ │ │ ├── controller.h │ │ │ ├── controller_common.h │ │ │ ├── cv_gate_controller.cpp │ │ │ ├── cv_gate_controller.h │ │ │ ├── keyboard_controller.cpp │ │ │ ├── keyboard_controller.h │ │ │ ├── midi_controller.cpp │ │ │ ├── midi_controller.h │ │ │ ├── osc_controller.cpp │ │ │ ├── osc_controller.h │ │ │ ├── parameter_controller.cpp │ │ │ ├── parameter_controller.h │ │ │ ├── program_controller.cpp │ │ │ ├── program_controller.h │ │ │ ├── real_time_controller.cpp │ │ │ ├── real_time_controller.h │ │ │ ├── session_controller.cpp │ │ │ ├── session_controller.h │ │ │ ├── system_controller.cpp │ │ │ ├── system_controller.h │ │ │ ├── timing_controller.cpp │ │ │ ├── timing_controller.h │ │ │ ├── transport_controller.cpp │ │ │ └── transport_controller.h │ │ ├── event_dispatcher.cpp │ │ ├── event_dispatcher.h │ │ ├── event_timer.cpp │ │ ├── event_timer.h │ │ ├── host_control.h │ │ ├── json_configurator.cpp │ │ ├── json_configurator.h │ │ ├── json_schemas/ │ │ │ ├── cv_gate_schema.json │ │ │ ├── events_schema.json │ │ │ ├── host_config_schema.json │ │ │ ├── midi_schema.json │ │ │ ├── osc_schema.json │ │ │ ├── state_schema.json │ │ │ └── tracks_schema.json │ │ ├── link_dummy.h │ │ ├── midi_dispatcher.cpp │ │ ├── midi_dispatcher.h │ │ ├── midi_receiver.h │ │ ├── parameter_manager.cpp │ │ ├── parameter_manager.h │ │ ├── plugin_library.cpp │ │ ├── plugin_library.h │ │ ├── processor_container.cpp │ │ ├── processor_container.h │ │ ├── receiver.cpp │ │ ├── receiver.h │ │ ├── track.cpp │ │ ├── track.h │ │ ├── transport.cpp │ │ └── transport.h │ ├── factories/ │ │ ├── base_factory.cpp │ │ ├── base_factory.h │ │ ├── offline_factory.cpp │ │ ├── offline_factory_implementation.cpp │ │ ├── offline_factory_implementation.h │ │ ├── reactive_factory.cpp │ │ ├── reactive_factory_implementation.cpp │ │ ├── reactive_factory_implementation.h │ │ ├── standalone_factory.cpp │ │ ├── standalone_factory_implementation.cpp │ │ └── standalone_factory_implementation.h │ ├── library/ │ │ ├── base_performance_timer.h │ │ ├── base_processor_factory.h │ │ ├── connection_types.h │ │ ├── event.cpp │ │ ├── event.h │ │ ├── event_interface.h │ │ ├── fixed_stack.h │ │ ├── id_generator.h │ │ ├── internal_plugin.cpp │ │ ├── internal_plugin.h │ │ ├── internal_processor_factory.cpp │ │ ├── internal_processor_factory.h │ │ ├── lv2/ │ │ │ ├── lv2_control.cpp │ │ │ ├── lv2_control.h │ │ │ ├── lv2_features.cpp │ │ │ ├── lv2_features.h │ │ │ ├── lv2_host_nodes.h │ │ │ ├── lv2_model.cpp │ │ │ ├── lv2_model.h │ │ │ ├── lv2_port.cpp │ │ │ ├── lv2_port.h │ │ │ ├── lv2_processor_factory.cpp │ │ │ ├── lv2_processor_factory.h │ │ │ ├── lv2_state.cpp │ │ │ ├── lv2_state.h │ │ │ ├── lv2_worker.cpp │ │ │ ├── lv2_worker.h │ │ │ ├── lv2_wrapper.cpp │ │ │ └── lv2_wrapper.h │ │ ├── midi_decoder.cpp │ │ ├── midi_decoder.h │ │ ├── midi_encoder.cpp │ │ ├── midi_encoder.h │ │ ├── parameter_dump.cpp │ │ ├── performance_timer.cpp │ │ ├── performance_timer.h │ │ ├── plugin_parameters.h │ │ ├── plugin_registry.cpp │ │ ├── plugin_registry.h │ │ ├── processor.cpp │ │ ├── processor.h │ │ ├── processor_state.cpp │ │ ├── processor_state.h │ │ ├── rt_event.h │ │ ├── rt_event_fifo.h │ │ ├── rt_event_pipe.h │ │ ├── simple_fifo.h │ │ ├── spinlock.h │ │ ├── synchronised_fifo.h │ │ ├── vst2x/ │ │ │ ├── vst2x_host_callback.cpp │ │ │ ├── vst2x_host_callback.h │ │ │ ├── vst2x_midi_event_fifo.h │ │ │ ├── vst2x_plugin_loader.cpp │ │ │ ├── vst2x_plugin_loader.h │ │ │ ├── vst2x_processor_factory.cpp │ │ │ ├── vst2x_processor_factory.h │ │ │ ├── vst2x_wrapper.cpp │ │ │ └── vst2x_wrapper.h │ │ └── vst3x/ │ │ ├── vst3x_file_utils.cpp │ │ ├── vst3x_file_utils.h │ │ ├── vst3x_host_app.cpp │ │ ├── vst3x_host_app.h │ │ ├── vst3x_processor_factory.cpp │ │ ├── vst3x_processor_factory.h │ │ ├── vst3x_utils.cpp │ │ ├── vst3x_utils.h │ │ ├── vst3x_wrapper.cpp │ │ └── vst3x_wrapper.h │ ├── plugins/ │ │ ├── arpeggiator_plugin.cpp │ │ ├── arpeggiator_plugin.h │ │ ├── brickworks/ │ │ │ ├── bitcrusher_plugin.cpp │ │ │ ├── bitcrusher_plugin.h │ │ │ ├── cab_sim_plugin.cpp │ │ │ ├── cab_sim_plugin.h │ │ │ ├── chorus_plugin.cpp │ │ │ ├── chorus_plugin.h │ │ │ ├── clip_plugin.cpp │ │ │ ├── clip_plugin.h │ │ │ ├── combdelay_plugin.cpp │ │ │ ├── combdelay_plugin.h │ │ │ ├── compressor_plugin.cpp │ │ │ ├── compressor_plugin.h │ │ │ ├── dist_plugin.cpp │ │ │ ├── dist_plugin.h │ │ │ ├── drive_plugin.cpp │ │ │ ├── drive_plugin.h │ │ │ ├── eq3band_plugin.cpp │ │ │ ├── eq3band_plugin.h │ │ │ ├── flanger_plugin.cpp │ │ │ ├── flanger_plugin.h │ │ │ ├── fuzz_plugin.cpp │ │ │ ├── fuzz_plugin.h │ │ │ ├── highpass_plugin.cpp │ │ │ ├── highpass_plugin.h │ │ │ ├── multi_filter_plugin.cpp │ │ │ ├── multi_filter_plugin.h │ │ │ ├── noise_gate_plugin.cpp │ │ │ ├── noise_gate_plugin.h │ │ │ ├── notch_plugin.cpp │ │ │ ├── notch_plugin.h │ │ │ ├── phaser_plugin.cpp │ │ │ ├── phaser_plugin.h │ │ │ ├── ring_mod_plugin.cpp │ │ │ ├── ring_mod_plugin.h │ │ │ ├── saturation_plugin.cpp │ │ │ ├── saturation_plugin.h │ │ │ ├── simple_synth_plugin.cpp │ │ │ ├── simple_synth_plugin.h │ │ │ ├── tremolo_plugin.cpp │ │ │ ├── tremolo_plugin.h │ │ │ ├── vibrato_plugin.cpp │ │ │ ├── vibrato_plugin.h │ │ │ ├── wah_plugin.cpp │ │ │ └── wah_plugin.h │ │ ├── control_to_cv_plugin.cpp │ │ ├── control_to_cv_plugin.h │ │ ├── cv_to_control_plugin.cpp │ │ ├── cv_to_control_plugin.h │ │ ├── equalizer_plugin.cpp │ │ ├── equalizer_plugin.h │ │ ├── freeverb_plugin.cpp │ │ ├── freeverb_plugin.h │ │ ├── gain_plugin.cpp │ │ ├── gain_plugin.h │ │ ├── lfo_plugin.cpp │ │ ├── lfo_plugin.h │ │ ├── mono_summing_plugin.cpp │ │ ├── mono_summing_plugin.h │ │ ├── passthrough_plugin.cpp │ │ ├── passthrough_plugin.h │ │ ├── peak_meter_plugin.cpp │ │ ├── peak_meter_plugin.h │ │ ├── return_plugin.cpp │ │ ├── return_plugin.h │ │ ├── sample_delay_plugin.cpp │ │ ├── sample_delay_plugin.h │ │ ├── sample_player_plugin.cpp │ │ ├── sample_player_plugin.h │ │ ├── sample_player_voice.cpp │ │ ├── sample_player_voice.h │ │ ├── send_plugin.cpp │ │ ├── send_plugin.h │ │ ├── send_return_factory.cpp │ │ ├── send_return_factory.h │ │ ├── step_sequencer_plugin.cpp │ │ ├── step_sequencer_plugin.h │ │ ├── stereo_mixer_plugin.cpp │ │ ├── stereo_mixer_plugin.h │ │ ├── transposer_plugin.cpp │ │ ├── transposer_plugin.h │ │ ├── wav_streamer_plugin.cpp │ │ ├── wav_streamer_plugin.h │ │ ├── wav_writer_plugin.cpp │ │ └── wav_writer_plugin.h │ └── utils.cpp ├── test/ │ ├── CMakeLists.txt │ ├── data/ │ │ ├── config.json │ │ ├── config_single_stereo.json │ │ └── master_limiter_test_data.h │ ├── resources/ │ │ └── Info.plist.in │ └── unittests/ │ ├── audio_frontends/ │ │ ├── apple_coreaudio_frontend_test.cpp │ │ ├── jack_frontend_test.cpp │ │ ├── offline_frontend_test.cpp │ │ └── portaudio_frontend_test.cpp │ ├── control_frontends/ │ │ ├── osc_frontend_test.cpp │ │ └── oscpack_osc_messenger_test.cpp │ ├── dsp_library/ │ │ ├── envelope_test.cpp │ │ ├── master_limiter_test.cpp │ │ ├── sample_wrapper_test.cpp │ │ └── value_smoother_test.cpp │ ├── engine/ │ │ ├── audio_graph_test.cpp │ │ ├── controller_test.cpp │ │ ├── controllers/ │ │ │ ├── audio_graph_controller_test.cpp │ │ │ ├── audio_routing_controller_test.cpp │ │ │ ├── midi_controller_test.cpp │ │ │ ├── osc_controller_test.cpp │ │ │ ├── reactive_controller_test.cpp │ │ │ └── session_controller_test.cpp │ │ ├── engine_test.cpp │ │ ├── event_dispatcher_test.cpp │ │ ├── event_timer_test.cpp │ │ ├── factories/ │ │ │ └── factories_test.cpp │ │ ├── json_configurator_test.cpp │ │ ├── midi_dispatcher_test.cpp │ │ ├── parameter_manager_test.cpp │ │ ├── plugin_library_test.cpp │ │ ├── processor_container_test.cpp │ │ ├── receiver_test.cpp │ │ ├── track_test.cpp │ │ └── transport_test.cpp │ ├── library/ │ │ ├── event_test.cpp │ │ ├── fixed_stack_test.cpp │ │ ├── id_generator_test.cpp │ │ ├── internal_plugin_test.cpp │ │ ├── lv2_wrapper_test.cpp │ │ ├── midi_decoder_test.cpp │ │ ├── midi_encoder_test.cpp │ │ ├── parameter_dump_test.cpp │ │ ├── performance_timer_test.cpp │ │ ├── plugin_parameters_test.cpp │ │ ├── processor_test.cpp │ │ ├── rt_event_test.cpp │ │ ├── sample_buffer_test.cpp │ │ ├── simple_fifo_test.cpp │ │ ├── vst2x_midi_event_fifo_test.cpp │ │ ├── vst2x_plugin_loading_test.cpp │ │ ├── vst2x_wrapper_test.cpp │ │ └── vst3x_wrapper_test.cpp │ ├── plugins/ │ │ ├── arpeggiator_plugin_test.cpp │ │ ├── brickworks_simple_synth_test.cpp │ │ ├── control_to_cv_plugin_test.cpp │ │ ├── cv_to_control_plugin_test.cpp │ │ ├── external_plugins_test.cpp │ │ ├── plugins_test.cpp │ │ ├── sample_player_plugin_test.cpp │ │ ├── send_return_test.cpp │ │ ├── step_sequencer_test.cpp │ │ └── wav_streamer_plugin_test.cpp │ ├── sample_test.cpp │ └── test_utils/ │ ├── apple_coreaudio_mockup.cpp │ ├── apple_coreaudio_mockup.h │ ├── audio_frontend_mockup.h │ ├── audio_graph_accessor.h │ ├── control_mockup.h │ ├── dummy_processor.h │ ├── engine_mockup.h │ ├── event_dispatcher_accessor.h │ ├── host_control_mockup.h │ ├── jack_mockup.cpp │ ├── meta_schema_v4.json │ ├── mock_event_dispatcher.h │ ├── mock_midi_frontend.h │ ├── mock_osc_interface.h │ ├── mock_oscpack.h │ ├── mock_processor_container.h │ ├── mock_sushi.h │ ├── plugin_accessors.h │ ├── portaudio_mockup.cpp │ ├── portaudio_mockup.h │ ├── test_utils.h │ ├── track_accessor.h │ ├── vst2_test_plugin.cpp │ ├── vst2_test_plugin.def │ ├── vst2_test_plugin.h │ ├── vst3_test_plugin.cpp │ └── vst3_test_plugin.h ├── third-party/ │ ├── .gitkeep │ ├── CMakeLists.txt │ ├── fifo/ │ │ ├── CMakeLists.txt │ │ └── include/ │ │ └── fifo/ │ │ └── circularfifo_memory_relaxed_aquire_release.h │ ├── lv2_host/ │ │ ├── CMakeLists.txt │ │ ├── include/ │ │ │ └── lv2_host/ │ │ │ ├── lv2_evbuf.h │ │ │ └── lv2_symap.h │ │ └── src/ │ │ ├── lv2_evbuf.cpp │ │ └── lv2_symap.cpp │ ├── optionparser/ │ │ ├── Makefile │ │ ├── example.cpp │ │ ├── example_arg.cc │ │ ├── optionparser.h │ │ ├── printUsage.h │ │ ├── testodr1.cc │ │ ├── testodr2.cc │ │ ├── testparse.cpp │ │ └── testprintusage.cpp │ └── vst3sdk.windows.patch ├── triplets/ │ └── win-custom-x86-64.cmake └── vcpkg.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # Generated from CLion C/C++ Code Style settings BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignOperands: true AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: Always AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: false AfterClass: true AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: false AfterUnion: true BeforeCatch: false BeforeElse: true IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: true BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon ColumnLimit: 0 CompactNamespaces: false ContinuationIndentWidth: 8 IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: true MaxEmptyLinesToKeep: 2 NamespaceIndentation: None ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PointerAlignment: Left ReflowComments: false SpaceAfterCStyleCast: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false TabWidth: 4 UseTab: Never SortIncludes: Never ================================================ FILE: .gitignore ================================================ # Object files *.o *.ko *.obj *.elf # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ # Hidden files .* !.clang-format !*/.gitkeep !.gitignore # Build and scratch directories scratch/ */scratch build*/ generated/ cmake-build-* third-party/install/ third-party/rehost/ # Windows folders *.ini # Python pyc *.pyc # CMake's compile_commnads used by clang-tools compile_commands.json # Log files created by running sushi from somewhere in the repository log.txt /vcpkg_installed/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.28) # Require out-of-source builds file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) if(EXISTS "${LOC_PATH}") message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") endif() ##################### # Project Version # ##################### # Don't change version anywhere else. Everything is generated from this. set(SUSHI_VERSION_MAJOR 1) set(SUSHI_VERSION_MINOR 3) set(SUSHI_VERSION_REVISION 0) # Versioning for the external api(s) # Update this to match the version above only if the external api (in include/sushi and/or rpc_interface/protos) # has been changed. Otherwise don't update. set(SUSHI_EXTERNAL_API_VERSION "1.2.0") project(sushi_library DESCRIPTION "Headless plugin host for Elk Audio OS" HOMEPAGE_URL "https://github.com/elk-audio/sushi" LANGUAGES CXX VERSION ${SUSHI_VERSION_MAJOR}.${SUSHI_VERSION_MINOR}.${SUSHI_VERSION_REVISION} ) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) ################################## # Generate build information # ################################## # Get the latest commit hash of the working branch execute_process( COMMAND git log -1 --format=%H WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) string(TIMESTAMP BUILD_TIMESTAMP "%Y-%m-%d %H:%M") configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in ${CMAKE_BINARY_DIR}/generated/version.h ) ########################### # Build and link options # ########################### # The defaults enable all options and select APIs available for either Xenomai or macOS set(SUSHI_WITH_JACK_DEFAULT OFF) set(SUSHI_WITH_VST2_DEFAULT OFF) set(SUSHI_WITH_VST3_DEFAULT ON) set(SUSHI_LINK_VST3_DEFAULT ON) set(SUSHI_WITH_LINK_DEFAULT ON) set(SUSHI_WITH_RPC_INTERFACE_DEFAULT ON) set(SUSHI_TWINE_STATIC_DEFAULT OFF) set(SUSHI_AUDIO_BUFFER_SIZE_DEFAULT 64) set(SUSHI_WITH_SENTRY_DEFAULT OFF) set(SUSHI_SENTRY_DSN_DEFAULT "--Sentry default DSN is undefined--") set(SUSHI_DISABLE_MULTICORE_UNIT_TESTS_DEFAULT OFF) set(SUSHI_BUILD_STANDALONE_APP_DEFAULT ON) set(SUSHI_BUILD_WITH_SANITIZERS_DEFAULT OFF) set(SUSHI_RASPA_FLAVOR_DEFAULT "evl") set(TWINE_WITH_XENOMAI_DEFAULT OFF) set(TWINE_WITH_EVL_DEFAULT OFF) if (APPLE) # macOS defaults set(SUSHI_WITH_RASPA_DEFAULT OFF) set(SUSHI_WITH_PORTAUDIO_DEFAULT OFF) set(SUSHI_WITH_APPLE_COREAUDIO_DEFAULT ON) set(SUSHI_WITH_ALSA_MIDI_DEFAULT OFF) set(SUSHI_WITH_RT_MIDI_DEFAULT ON) set(SUSHI_WITH_VST2_DEFAULT OFF) set(SUSHI_WITH_LV2_DEFAULT ON) set(SUSHI_WITH_LV2_MDA_TESTS_DEFAULT OFF) set(SUSHI_WITH_UNIT_TESTS_DEFAULT ON) set(SUSHI_BUILD_TWINE_DEFAULT ON) set(SUSHI_DISABLE_MULTICORE_UNIT_TESTS_DEFAULT ON) elseif (MSVC) # windows defaults set(SUSHI_WITH_XENOMAI_DEFAULT OFF) set(SUSHI_WITH_PORTAUDIO_DEFAULT ON) set(SUSHI_WITH_ALSA_MIDI_DEFAULT OFF) set(SUSHI_WITH_RT_MIDI_DEFAULT ON) set(SUSHI_WITH_VST2_DEFAULT OFF) set(SUSHI_WITH_LV2_DEFAULT OFF) set(SUSHI_WITH_LV2_MDA_TESTS_DEFAULT OFF) set(SUSHI_WITH_UNIT_TESTS_DEFAULT ON) set(SUSHI_BUILD_TWINE_DEFAULT ON) add_compile_definitions(_USE_MATH_DEFINES) elseif (CMAKE_CROSSCOMPILING) # Yocto cross-compilation defaults set(SUSHI_WITH_RASPA_DEFAULT ON) set(SUSHI_WITH_PORTAUDIO_DEFAULT OFF) set(SUSHI_WITH_APPLE_COREAUDIO_DEFAULT OFF) set(SUSHI_WITH_ALSA_MIDI_DEFAULT ON) set(SUSHI_WITH_RT_MIDI_DEFAULT OFF) set(SUSHI_WITH_LV2_DEFAULT ON) set(SUSHI_WITH_LV2_MDA_TESTS_DEFAULT OFF) set(SUSHI_WITH_UNIT_TESTS_DEFAULT OFF) set(SUSHI_BUILD_TWINE_DEFAULT OFF) else() # Native Linux defaults set(SUSHI_WITH_JACK_DEFAULT ON) set(SUSHI_WITH_RASPA_DEFAULT OFF) set(SUSHI_WITH_PORTAUDIO_DEFAULT OFF) set(SUSHI_WITH_APPLE_COREAUDIO_DEFAULT OFF) set(SUSHI_WITH_ALSA_MIDI_DEFAULT ON) set(SUSHI_WITH_RT_MIDI_DEFAULT OFF) set(SUSHI_WITH_LV2_DEFAULT ON) set(SUSHI_WITH_LV2_MDA_TESTS_DEFAULT ON) set(SUSHI_WITH_UNIT_TESTS_DEFAULT ON) set(SUSHI_BUILD_TWINE_DEFAULT ON) endif() option(SUSHI_WITH_RASPA "Enable Raspa (xenomai) support" ${SUSHI_WITH_RASPA_DEFAULT}) option(SUSHI_WITH_JACK "Enable Jack support" ${SUSHI_WITH_JACK_DEFAULT}) option(SUSHI_WITH_PORTAUDIO "Enable PortAudio support" ${SUSHI_WITH_PORTAUDIO_DEFAULT}) option(SUSHI_WITH_APPLE_COREAUDIO "Enable Apple CoreAudio support" ${SUSHI_WITH_APPLE_COREAUDIO_DEFAULT}) option(SUSHI_WITH_ALSA_MIDI "Enable alsa midi support" ${SUSHI_WITH_ALSA_MIDI_DEFAULT}) option(SUSHI_WITH_RT_MIDI "Enable RtMidi support" ${SUSHI_WITH_RT_MIDI_DEFAULT}) option(SUSHI_WITH_VST2 "Enable Vst 2 support" ${SUSHI_WITH_VST2_DEFAULT}) option(SUSHI_WITH_VST3 "Enable Vst 3 support" ${SUSHI_WITH_VST3_DEFAULT}) option(SUSHI_LINK_VST3 "Link Vst 3 library to Sushi" ${SUSHI_LINK_VST3_DEFAULT}) option(SUSHI_WITH_LV2 "Enable LV2 support" ${SUSHI_WITH_LV2_DEFAULT}) option(SUSHI_WITH_LV2_MDA_TESTS "Include unit tests depending on LV2 drobilla MDA plugin port." ${SUSHI_WITH_LV2_MDA_TESTS_DEFAULT}) option(SUSHI_WITH_UNIT_TESTS "Build and run unit tests after compilation" ${SUSHI_WITH_UNIT_TESTS_DEFAULT}) option(SUSHI_WITH_LINK "Enable Ableton Link support" ${SUSHI_WITH_LINK_DEFAULT}) option(SUSHI_WITH_RPC_INTERFACE "Enable RPC control support" ${SUSHI_WITH_RPC_INTERFACE_DEFAULT}) option(SUSHI_BUILD_TWINE "Build included Twine library" ${SUSHI_BUILD_TWINE_DEFAULT}) option(SUSHI_TWINE_STATIC "Link against static version of TWINE library" ${SUSHI_TWINE_STATIC_DEFAULT}) option(SUSHI_WITH_SENTRY "Enable sentry.io support" ${SUSHI_WITH_SENTRY_DEFAULT}) option(SUSHI_SENTRY_DSN "Required Sentry DSN Path" ${SUSHI_SENTRY_DSN_DEFAULT}) option(SUSHI_DISABLE_MULTICORE_UNIT_TESTS "Disable unit-tests dependent on multi-core processing." ${SUSHI_DISABLE_MULTICORE_UNIT_TESTS_DEFAULT}) option(SUSHI_BUILD_STANDALONE_APP "Build standalone Sushi executable" ${SUSHI_BUILD_STANDALONE_APP_DEFAULT}) option(SUSHI_BUILD_WITH_SANITIZERS "Build Sushi with google address sanitizer on" ${SUSHI_BUILD_WITH_SANITIZERS_DEFAULT}) option(SUSHI_LINK_WITH_PIPEWIRE "Link sushi with pipewire, only use if linking with a rasba build with Pipewire support" OFF) set(SUSHI_AUDIO_BUFFER_SIZE ${SUSHI_AUDIO_BUFFER_SIZE_DEFAULT} CACHE STRING "Set internal audio buffer size in frames") set(SUSHI_RASPA_FLAVOR ${SUSHI_RASPA_FLAVOR_DEFAULT} CACHE STRING "Set raspa flavor. Options are xenomai or evl") if (SUSHI_WITH_ALSA_MIDI AND SUSHI_WITH_RT_MIDI) message(FATAL_ERROR "Both alsa midi and RtMidi set to on. Use only one midi frontend at a time") endif() if (SUSHI_WITH_RASPA) # Determine raspa evl/xenomai variables if (${SUSHI_RASPA_FLAVOR} MATCHES "evl") message("Building with Raspa/Twine EVL support") set(TWINE_WITH_EVL ON CACHE BOOL "" FORCE) elseif (${SUSHI_RASPA_FLAVOR} MATCHES "xenomai") message("Building with Raspa/Twine Xenomai support.") message("Warning Xenomai 3 support is deprecated and will be removed in future versions") set(TWINE_WITH_XENOMAI ON CACHE BOOL "" FORCE) else () message(FATAL_ERROR "Raspa flavor should be set to xenomai or evl") endif() endif() ############################### # Sub-directory configuration # ############################### if (${SUSHI_BUILD_TWINE}) set(TWINE_WITH_TESTS OFF CACHE BOOL "") add_subdirectory(twine) if (${SUSHI_TWINE_STATIC}) set(TWINE_LIB twine::twine_static) else() set(TWINE_LIB twine::twine) endif() set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${TWINE_LIB}) else() find_package(twine 1.1) set(INCLUDE_DIRS ${INCLUDE_DIRS} ${TWINE_PATH}) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${TWINE_LIB}) find_package(elk-warning-suppressor 0.1.0 COMPONENTS warningSuppressor) endif() set(ELKLOG_WITH_UNIT_TESTS OFF CACHE BOOL "") set(ELKLOG_USE_INCLUDED_TWINE OFF CACHE BOOL "") set(ELKLOG_TWINE_STATIC ${SUSHI_TWINE_STATIC} CACHE STRING "") set(ELKLOG_WITH_EXAMPLES OFF CACHE BOOL "") set(ELKLOG_MULTITHREADED_RT_LOGGING OFF CACHE BOOL "") set(ELKLOG_FILE_SIZE 10000000 CACHE STRING "") set(ELKLOG_RT_MESSAGE_SIZE 2048 CACHE STRING "") set(ELKLOG_RT_QUEUE_SIZE 1024 CACHE STRING "") add_subdirectory(elklog) add_subdirectory(third-party EXCLUDE_FROM_ALL) ############### # Raspa setup # ############### if (${SUSHI_WITH_RASPA}) find_library(RASPA_LIB NAMES raspa PATHS /usr/lib /usr/local/lib ) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${RASPA_LIB} asound) set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_RASPA) if (${SUSHI_LINK_WITH_PIPEWIRE}) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} pipewire-0.3) endif() if (NOT "$ENV{CMAKE_SYSROOT}" STREQUAL "") set(CMAKE_SYSROOT "$ENV{CMAKE_SYSROOT}") message("ENV_CMAKE_SYSROOT = " $ENV{CMAKE_SYSROOT}) endif() # Determine whether to link with evl or cobalt lib if (${SUSHI_RASPA_FLAVOR} MATCHES "xenomai") # External libraries: # explicitly link with cobalt lib set(XENOMAI_BASE_DIR "/usr/xenomai" CACHE STRING "xenomai base dir path") if (NOT "$ENV{CMAKE_SYSROOT}" STREQUAL "") set(CMAKE_SYSROOT "$ENV{CMAKE_SYSROOT}") message("ENV_CMAKE_SYSROOT = " $ENV{CMAKE_SYSROOT}) endif() if (NOT "${CMAKE_SYSROOT}" STREQUAL "") set(XENOMAI_BASE_DIR "${CMAKE_SYSROOT}/usr/xenomai") message("XENOMAI_BASE_DIR is " ${XENOMAI_BASE_DIR}) endif() find_library(COBALT_LIB cobalt HINTS ${XENOMAI_BASE_DIR}/lib) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${COBALT_LIB}) elseif (${SUSHI_RASPA_FLAVOR} MATCHES "evl") message("Building with EVL support") find_library(EVL_LIB NAMES evl PATHS /usr/lib /usr/local/lib ) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${EVL_LIB}) endif() endif() ############## # Jack setup # ############## if (${SUSHI_WITH_JACK}) message("Building with Jack support.") # Linked libraries find_path(jack_dir NAMES "jack/jack.h") find_library(jack_lib NAMES jack) set(INCLUDE_DIRS ${INCLUDE_DIRS} ${jack_dir}) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${jack_lib}) # Compile definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_JACK) endif() ################### # PortAudio setup # ################### if (${SUSHI_WITH_PORTAUDIO}) message("Building with PortAudio support.") set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} PortAudio) # Compile definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_PORTAUDIO) endif() if (APPLE) set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_APPLE_THREADING) ################### # CoreAudio setup # ################### if (${SUSHI_WITH_APPLE_COREAUDIO}) message("Building with Apple CoreAudio support.") # Compile definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_APPLE_COREAUDIO) set(ADDITIONAL_APPLE_COREAUDIO_SOURCES src/audio_frontends/apple_coreaudio/apple_coreaudio_utils.cpp src/audio_frontends/apple_coreaudio/apple_coreaudio_object.cpp src/audio_frontends/apple_coreaudio/apple_coreaudio_device.mm) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} "-framework CoreAudio -framework Foundation") endif() endif() ################### # Alsa midi setup # ################### if (${SUSHI_WITH_ALSA_MIDI}) message("Building with alsa midi support.") # Additional sources set(MIDI_SOURCES src/control_frontends/alsa_midi_frontend.h src/control_frontends/alsa_midi_frontend.cpp) # linked libraries find_path(ALSA_DIR NAMES "alsa/asoundlib.h") find_library(ALSA_LIB NAMES asound) set(INCLUDE_DIRS ${INCLUDE_DIRS} ${ALSA_DIR}) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} ${ALSA_LIB}) # Compile definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_ALSA_MIDI) endif() ################ # RtMidi setup # ################ if (${SUSHI_WITH_RT_MIDI}) message("Building with RtMidi support.") # Additional sources set(MIDI_SOURCES ${MIDI_SOURCES} src/control_frontends/rt_midi_frontend.cpp src/control_frontends/rt_midi_frontend.h) #RtMidi library find_package(RtMidi CONFIG REQUIRED) set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} RtMidi::rtmidi) set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_RT_MIDI) endif() ############### # VST 2 setup # ############### if (${SUSHI_WITH_VST2}) message("Building with Vst2 support.") set(ADDITIONAL_VST2_SOURCES src/library/vst2x/vst2x_wrapper.h src/library/vst2x/vst2x_host_callback.h src/library/vst2x/vst2x_plugin_loader.h src/library/vst2x/vst2x_midi_event_fifo.h src/library/vst2x/vst2x_processor_factory.h src/library/vst2x/vst2x_wrapper.cpp src/library/vst2x/vst2x_host_callback.cpp src/library/vst2x/vst2x_plugin_loader.cpp) # Include dirs if (VST_2_SDK_BASE_DIR) set(SUSHI_VST2_SDK_PATH ${VST_2_SDK_BASE_DIR}) else() find_file(SUSHI_VST2_SDK_PATH NAMES VST2_SDK vstsdk2.4 HINTS $ENV{HOME}/workspace $ENV{HOME}/workspace/VST_SDK $ENV{HOME}/workspace/vstsdk3610_11_06_2018_build_37) endif() if (${SUSHI_VST2_SDK_PATH} STREQUAL "VST2_SDK_PATH-NOTFOUND") message("Warning! Could not find Steinberg SDK for VST 2.4") endif() set(INCLUDE_DIRS ${INCLUDE_DIRS} ${SUSHI_VST2_SDK_PATH}/pluginterfaces/vst2.x) # Compile definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_VST2 -D__cdecl=) endif() ############### # VST 3 setup # ############### # TODO: use this ${SUSHI_LINK_VST3}, and when it's off, link to VST3 SDK included by JUCE. if (${SUSHI_WITH_VST3}) message("Building with Vst3 support.") # Vst 3 host: # Build the given Vst3 host implementation into a separate library to avoid # Vst specific defines leaking into our code. set(VST3_SDK_PATH "${PROJECT_SOURCE_DIR}/third-party/vst3sdk") set(VST3_HOST_SOURCES "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/eventlist.cpp" "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/parameterchanges.cpp" "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/hostclasses.cpp" "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/module.cpp" "${VST3_SDK_PATH}/public.sdk/source/vst/utility/stringconvert.cpp" "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp" "${VST3_SDK_PATH}/public.sdk/source/common/memorystream.cpp") if (MSVC) set(VST3_HOST_SOURCES ${VST3_HOST_SOURCES} "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/module_win32.cpp") endif() if(NOT MSVC) set(VST3_COMPILE_FLAGS "-Wno-unused-parameter \ -Wno-extra \ -Wno-deprecated \ -Wno-cpp \ -Wno-pointer-bool-conversion \ -Wno-suggest-override \ -Wno-deprecated-declarations \ -Wno-shorten-64-to-32 \ -Wformat=0") endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(VST3_HOST_SOURCES ${VST3_HOST_SOURCES} "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/module_linux.cpp") set(AUDIOHOST_PLATFORM_LIBS) endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(VST3_HOST_SOURCES ${VST3_HOST_SOURCES} "${VST3_SDK_PATH}/public.sdk/source/vst/hosting/module_mac.mm") set(AUDIOHOST_PLATFORM_LIBS "-framework Cocoa") set(VST3_COMPILE_FLAGS "${VST3_COMPILE_FLAGS} -fobjc-arc -Wno-unqualified-std-cast-call") endif() add_library(vst3_host STATIC ${VST3_HOST_SOURCES}) target_link_libraries(vst3_host PRIVATE ${AUDIOHOST_PLATFORM_LIBS} elk_vst3_extensions) set_target_properties(vst3_host PROPERTIES EXCLUDE_FROM_ALL true) target_compile_features(vst3_host PUBLIC cxx_std_20) if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") set_target_properties(vst3_host PROPERTIES COMPILE_FLAGS "-DRELEASE ${VST3_COMPILE_FLAGS}") else() set_target_properties(vst3_host PROPERTIES COMPILE_FLAGS "-DDEVELOPMENT ${VST3_COMPILE_FLAGS}") endif() target_include_directories(vst3_host PRIVATE "${PROJECT_SOURCE_DIR}/third-party/vst3sdk") add_dependencies(vst3_host sdk) # Set sources set(ADDITIONAL_VST3_SOURCES src/library/vst3x/vst3x_wrapper.h src/library/vst3x/vst3x_host_app.h src/library/vst3x/vst3x_utils.h src/library/vst3x/vst3x_processor_factory.h src/library/vst3x/vst3x_wrapper.cpp src/library/vst3x/vst3x_host_app.cpp src/library/vst3x/vst3x_utils.cpp src/library/vst3x/vst3x_file_utils.cpp) # Linked libraries set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} vst3_host sdk elk_vst3_extensions) # Compiler definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_VST3) # In yocto env / cross compiling scenarios, this is not needed as mda-vst3 plugins are # built already. Also, this causes an "exec-format" error since the plugins need a specific # method of compilation during cross compilation process. if (NOT CMAKE_CROSSCOMPILING) # Triggering mda-vst3 build - used by sample sushi json configurations under misc/config_files. set_target_properties(mda-vst3 PROPERTIES EXCLUDE_FROM_ALL False) endif() endif() ############# # LV2 setup # ############# if (${SUSHI_WITH_LV2}) message("Building with LV2 support.") find_package(lv2 CONFIG REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(LILV REQUIRED IMPORTED_TARGET lilv-0) set(ADDITIONAL_LV2_SOURCES src/library/lv2/lv2_wrapper.h src/library/lv2/lv2_control.h src/library/lv2/lv2_features.h src/library/lv2/lv2_host_nodes.h src/library/lv2/lv2_model.h src/library/lv2/lv2_port.h src/library/lv2/lv2_state.h src/library/lv2/lv2_processor_factory.h src/library/lv2/lv2_wrapper.cpp src/library/lv2/lv2_state.cpp src/library/lv2/lv2_worker.cpp src/library/lv2/lv2_features.cpp src/library/lv2/lv2_model.cpp src/library/lv2/lv2_port.cpp src/library/lv2/lv2_control.cpp) # Linked libraries set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} PkgConfig::LILV lv2_host) # Compiler definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_LV2) endif() ############## # gRPC setup # ############## if (${SUSHI_WITH_RPC_INTERFACE}) message("Building with RPC support.") add_subdirectory(rpc_interface) # Linked libraries set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} sushi_rpc) # Compiler definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_RPC_INTERFACE) endif() ###################### # Ableton link setup # ###################### if (${SUSHI_WITH_LINK}) message("Building with Ableton Link support.") include(third-party/link/AbletonLinkConfig.cmake) # Linked libraries set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} Ableton::Link) # Compiler definitions set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_ABLETON_LINK) endif() message("Configured audio buffer size: " ${SUSHI_AUDIO_BUFFER_SIZE} " samples") ################### # sentry.io setup # ################### if (${SUSHI_WITH_SENTRY}) find_package(Threads) find_package(ZLIB) find_package(sentry CONFIG REQUIRED) message("Building with sentry.io support.") if (NOT SUSHI_SENTRY_DSN) set(SUSHI_SENTRY_DSN ${SUSHI_SENTRY_DSN_DEFAULT} CACHE STRING "The DSN to upload the sentry logs and crash reports to, from Sushi" FORCE) endif() set(SENTRY_CRASHPAD_HANDLER_PATH "${VCPKG_INSTALLED_DIR}/osx-custom/tools/sentry-native/crashpad_handler") set(EXTRA_BUILD_LIBRARIES ${EXTRA_BUILD_LIBRARIES} sentry::sentry) set(EXTRA_COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS} -DSUSHI_BUILD_WITH_SENTRY -DSUSHI_SENTRY_DSN="${SUSHI_SENTRY_DSN}") endif() #################### # Main Target # #################### set(SOURCE_FILES src/utils.cpp src/audio_frontends/base_audio_frontend.cpp src/concrete_sushi.cpp src/audio_frontends/offline_frontend.cpp src/audio_frontends/reactive_frontend.cpp src/audio_frontends/jack_frontend.cpp src/audio_frontends/portaudio_frontend.cpp src/audio_frontends/apple_coreaudio_frontend.cpp src/audio_frontends/portaudio_devices_dump.cpp src/audio_frontends/coreaudio_devices_dump.cpp src/audio_frontends/xenomai_raspa_frontend.cpp src/audio_frontends/offline_frontend.cpp src/audio_frontends/reactive_frontend.cpp src/control_frontends/base_control_frontend.cpp src/control_frontends/oscpack_osc_messenger.cpp src/control_frontends/reactive_midi_frontend.cpp src/control_frontends/osc_frontend.cpp src/dsp_library/biquad_filter.cpp src/engine/audio_engine.cpp src/engine/audio_graph.cpp src/engine/event_dispatcher.cpp src/engine/track.cpp src/engine/midi_dispatcher.cpp src/engine/json_configurator.cpp src/engine/receiver.cpp src/engine/event_timer.cpp src/engine/transport.cpp src/engine/parameter_manager.cpp src/engine/processor_container.cpp src/engine/plugin_library.cpp src/engine/controller/controller.cpp src/engine/controller/system_controller.cpp src/engine/controller/transport_controller.cpp src/engine/controller/timing_controller.cpp src/engine/controller/keyboard_controller.cpp src/engine/controller/audio_graph_controller.cpp src/engine/controller/parameter_controller.cpp src/engine/controller/program_controller.cpp src/engine/controller/midi_controller.cpp src/engine/controller/audio_routing_controller.cpp src/engine/controller/cv_gate_controller.cpp src/engine/controller/osc_controller.cpp src/engine/controller/session_controller.cpp src/engine/controller/real_time_controller.cpp src/factories/base_factory.cpp src/factories/reactive_factory.cpp src/factories/reactive_factory_implementation.cpp src/factories/standalone_factory.cpp src/factories/standalone_factory_implementation.cpp src/factories/offline_factory.cpp src/factories/offline_factory_implementation.cpp src/library/event.cpp src/library/midi_decoder.cpp src/library/midi_encoder.cpp src/library/internal_plugin.cpp src/library/performance_timer.cpp src/library/parameter_dump.cpp src/library/processor.cpp src/library/processor_state.cpp src/library/plugin_registry.cpp src/library/internal_processor_factory.cpp src/library/lv2/lv2_processor_factory.cpp src/library/vst2x/vst2x_processor_factory.cpp src/library/vst3x/vst3x_processor_factory.cpp src/plugins/arpeggiator_plugin.cpp src/plugins/control_to_cv_plugin.cpp src/plugins/cv_to_control_plugin.cpp src/plugins/gain_plugin.cpp src/plugins/lfo_plugin.cpp src/plugins/passthrough_plugin.cpp src/plugins/equalizer_plugin.cpp src/plugins/freeverb_plugin.cpp src/plugins/peak_meter_plugin.cpp src/plugins/return_plugin.cpp src/plugins/sample_player_plugin.cpp src/plugins/sample_player_voice.cpp src/plugins/sample_delay_plugin.cpp src/plugins/send_plugin.cpp src/plugins/send_return_factory.cpp src/plugins/step_sequencer_plugin.cpp src/plugins/transposer_plugin.cpp src/plugins/wav_streamer_plugin.cpp src/plugins/wav_writer_plugin.cpp src/plugins/mono_summing_plugin.cpp src/plugins/stereo_mixer_plugin.cpp src/plugins/brickworks/compressor_plugin.cpp src/plugins/brickworks/cab_sim_plugin.cpp src/plugins/brickworks/ring_mod_plugin.cpp src/plugins/brickworks/bitcrusher_plugin.cpp src/plugins/brickworks/wah_plugin.cpp src/plugins/brickworks/eq3band_plugin.cpp src/plugins/brickworks/phaser_plugin.cpp src/plugins/brickworks/chorus_plugin.cpp src/plugins/brickworks/vibrato_plugin.cpp src/plugins/brickworks/flanger_plugin.cpp src/plugins/brickworks/combdelay_plugin.cpp src/plugins/brickworks/saturation_plugin.cpp src/plugins/brickworks/noise_gate_plugin.cpp src/plugins/brickworks/tremolo_plugin.cpp src/plugins/brickworks/notch_plugin.cpp src/plugins/brickworks/multi_filter_plugin.cpp src/plugins/brickworks/highpass_plugin.cpp src/plugins/brickworks/clip_plugin.cpp src/plugins/brickworks/fuzz_plugin.cpp src/plugins/brickworks/dist_plugin.cpp src/plugins/brickworks/drive_plugin.cpp src/plugins/brickworks/simple_synth_plugin.cpp ) add_library(${PROJECT_NAME} STATIC "${SOURCE_FILES}" "${ADDITIONAL_APPLE_COREAUDIO_SOURCES}" "${ADDITIONAL_VST2_SOURCES}" "${ADDITIONAL_VST3_SOURCES}" "${ADDITIONAL_LV2_SOURCES}" "${MIDI_SOURCES}") set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) ######################### # Include Directories # ######################### set(INCLUDE_DIRS ${INCLUDE_DIRS} "${PROJECT_SOURCE_DIR}/src" "${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}/third-party/vst3sdk" "${PROJECT_SOURCE_DIR}/third-party/link/include" "${PROJECT_SOURCE_DIR}/third-party/brickworks/include" ) set(PUBLIC_INCLUDE_DIRS "${CMAKE_BINARY_DIR}" # for generated version.h "${PROJECT_SOURCE_DIR}/third-party/optionparser/" "${PROJECT_SOURCE_DIR}/third-party/rapidjson/include") ################################# # Linked libraries # ################################# find_package(SndFile CONFIG REQUIRED) set(COMMON_LIBRARIES SndFile::sndfile fifo oscpack elklog freeverb elk::warningSuppressor ${TWINE_LIB} ) if(NOT MSVC) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} pthread dl) set_target_properties(oscpack PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations") endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") set(COMMON_LIBRARIES ${COMMON_LIBRARIES} atomic) endif() target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRS} PUBLIC ${PUBLIC_INCLUDE_DIRS} # where top-level project will look for the library's public headers $ ) target_link_libraries(${PROJECT_NAME} PUBLIC ${EXTRA_BUILD_LIBRARIES} ${COMMON_LIBRARIES}) #################################### # Compiler Flags and definitions # #################################### if(MSVC) # C5045: Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified # is disabled for all - Spectre is not a concern for Sushi I don't think. # C4996: Deprecated warning. This was flooding the terminal, # I might remove the suppression from here eventually, and out into the code again. target_compile_options(${PROJECT_NAME} PRIVATE /wd4996 /wd5045) else () target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Wno-psabi -fPIC -ffast-math) target_compile_options(${PROJECT_NAME} PRIVATE -fno-rtti) if (SUSHI_BUILD_WITH_SANITIZERS) target_compile_options(${PROJECT_NAME} PUBLIC -fsanitize=address -g) endif() endif() target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) target_compile_definitions(${PROJECT_NAME} PUBLIC -DSUSHI_CUSTOM_AUDIO_CHUNK_SIZE=${SUSHI_AUDIO_BUFFER_SIZE} ${EXTRA_COMPILE_DEFINITIONS}) ##################### # Sub projects # ##################### if (${SUSHI_BUILD_STANDALONE_APP}) add_subdirectory(apps) endif() ###################### # Tests subproject # ###################### if (${SUSHI_WITH_UNIT_TESTS}) add_subdirectory(test) endif() ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Elk Audio OS ## Github Issues You can open a Github issue to signal a bug or request a new feature. Please don't create an issue to ask about general help, use the [Elk Audio Forum](https://forum.elk.audio) for generic questions and assistance. When writing a bug report, try to be as complete as you can regarding the context: * Mention the version of Elk Audio OS that you are testing * What is your run-time setup (plugins, buffer size, audio/CV channels, sensors, etc.) * (if working on a plugin) what is your build environment * If the issue is about SUSHI, TWINE or a plugin, try to reproduce it with the SUSHI AppImage for Linux or using another audio frontend (offline, dummy) * Include relevant files that helps understand your issue or, better, to reproduce it ## Submitting a PR A signed Contributor Agreement (CA) is required for submitting Pull Requests to this repository. Please contact tech@elk.audio for more information. ================================================ FILE: COPYING ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: HISTORY.md ================================================ ## 1.3.0 New Features: * Tracks can be explicitly allocated to threads * Send and return plugins are now zero delay under some conditions * Updated vcpkg * Send and return plugins are now zero-delay if on the same cpu core * Vst3 extension apis * Updated gRPC api with versioning and command completion notifications * Updated example config files to use initial state instead of events * String properties can now be output only, i.e. not settable by Sushi Fixes: * Warning if multiple twine workers are allocated to the same cpu core * Fixes for CMake 4 * Updates for deprecated features in C++20 * Refactored processor channel config to be less stateful * Fixed issue with VST2 plugins with > 16 channels * Vst3 non-automatable parameter bug fixed * Vst3 32 bit crash on load fixed ## 1.2.0 New Features: * Support for using Sushi as a library * Windows support * C++20 support * Updated Elklog * Updated Twine to 1.0 * Warning suppressor library included Fixes: * Improved handling of audio pause / resume * Xrun detection in audio frontends * Improved handling of processor add/delete if audio thread is not running * Better event lifetime management * EventDispatcher thread could stall if system clock changed * Fix for Vst2 plugins with > 16 channels * All example config files now uses "initial_state" to set initial parameter values ## 1.1.0 New features: * Added Apple CoreAudio frontend * Support for aggregate devices * Brickworks plugin suite added * Wave Streamer plugin * Freeverb plugin * TWINE updated to 0.4.0 with workgroup and EVL support * Rapidjson updated to latest master branch * Optimised State changes for VST3 plugins * Crash logging with Sentry * Device dump now includes sample rate and buffer size support Fixes: * LV2 worker request space too small * LV2 logging not handled correctly * Incorrect velocity scaling on Midi output from LV2 plugins * VST3 plugin initialization order fix * Parameter notification fix * Incorrect file length display in WaveStreamer plugin * Seek precision fix in WaveStreamer plugin * Audio channel setup now only called when plugin is disabled * Send and return crash at exit Breaking changes: * The argument --dump-portaudio-devs is changed to --dump-audio-devices and now requires a frontend to be specified (--coreaudio or --portaudio). * Failure to set priority and/or workgroup on macOS will cause Sushi to exit. ## 1.0.0 New features: * clang 13+ support * gcc 10+ support * macOS support * VST2 & VST3 bundle support for macOS * Portaudio frontend * RtMIDI frontend * MIDI clock output * Processor state save & reload * Plugin binary state setting * Session state save & reload * Refactor track modes w.r.t. channel configuration * "pre" and "post" tracks to process engine channels before/after dispatching them to tracks * Audio frontends pause/restart * Switch from liblo to oscpack for OSC * Option to set target OSC IP * Channel specification in send&return internal plugins * Extended parameter notifications, now multiple controllers can be updated easily at the same time * LV2 parameter output support * Command-line switches to disable OSC and gRPC * Use vcpkg to manage most of third-party dependencies * Refactored CMake configuration, now using SUSHI_ for options prefix * Ableton Link updated to v3.0.5 * TWINE updated to v0.3.2 * VST3 SDK updated to v3.7.6 Fixes: * Workaround for delayed ALSA MIDI event output * Crash in MIDI dispatcher when deleting track connections * Various JSON parsing fixes * Memory leak in VST3 wrapper * Issue with setting different samplerate from JSON and audio frontend * Slow response to large numbers of VST3 parameter changes * Peakmeter formatted level parameter scaling Breaking changes: * All build options renamed to SUSHI_XXX_YYY from XXX_YYY * Vcpgk initalization step required at first build. See README.md * Track channel config has been simplified. The json "mode" member has been replaced with an integer "channels" member. To create a multibus track, an additional member "multibus" must be set to true and the number of buses specified with the "buses" member. * Tracks now only report number of channels (and buses in the multibus case) and input and output channels and busses are assumed to be the same. * All instances of "busses" in json config and grpc api has been corrected to "buses" ## 0.12.0 New Features: * Internal send and return plugins * Stereo mixer internal plugin * Sample delay internal plugin * Multi channel support in peak meter plugin * Timing and transport notifications * Processor properties support * Master limiter * Updated VST SDK to v3.7.3 * Updated Twine to v0.2.1 * Build support for GCC10 Fixes: * gRPC segfault on exit * Refactored plugin instantiation architecture * Track noise for tracks with no audio input * Bug where events outputted before the audio process callback were lost * Race condition in OSC path registration * LV2 plugin load crash ## 0.11.0 New Features: * Expanded gRPC control interface, including push-style notifications * Bumped recommended gRPC version * Dynamic loading and routing of tracks and plugins * Configurable OSC parameter output * Wav writer plugin * Mono summing plugin * Improved peak meter plugin Fixes: * Aftertouch messages not forwarded in Alsa midi frontend * Ensuring silence when track gain is 0 * Fix for generate script when python2.7 is missing * Fix to accommodate v1.16 and v1.24 of gRPC libraries * Raspa frontend initialisation order fix * Internal event system refactor * Controller class refactor and split into sub-controllers * Logging library built statically - faster compile time * Fix for timing sensitive unit tests * Audio routing bug for mono tracks ## 0.10.3 Fixes: * Mono tracks can now be connected to stereo output busses * OSC paths for gain and pan controls on tracks work again ## 0.10.2 Fixes: * LV2 parameter handling fix for non-sequential parameter ids ## 0.10.1 New Features: * LV2 worker thread extension support * Parameter values are now always normalised Fixes: * LV2 string parameter value fix ## 0.10.0 New Features: * LV2 plugin support * Ableton Link support * Multiple midi input and output ports * Extended OSC control API making it more similar to gRPC Fixes: * Updated example configuration files ## 0.9.1 New Features: * Plugin information dump with CL switch * Updated VST 3 SDK to latest 3.14 Fixes: * Allows plugin parameters with duplicate names * Events section not required anymore with any frontend ## 0.9.0 New Features: * CV and Gate support + related example plugins * Step sequencer example plugin * gRPC listening address configurable through command line Fixes: * Parameter Units now passed correctly * Faster Json file loading * Better Json parsing error printing * Removed Raspalib submodule * Unit test fixes ## 0.8.0 New Features: * gRPC control API * Automatic clip detection on master tracks * Smooth track parameter controls at audio rate * Simple MIDI transposer internal plugin * Support relative mode for MIDI CCs mapping * (x86 / XMOS) : support in RASPA for USB Midi gadget * VST 2.x support is now an optional build feature * Support VST 3 programs * Added an option to automatically flush the logs at a periodic interval Fixes: * TWINE: Automatic denormal handling in all threads * Raspa: fix for affinity of non-RT threads after initialization * Raspa on x86/XMOS: fixes for spurious startup synchronization * MIDI: explicitly convert NoteON with zero velocity to NoteOFF messages * Various VST 3 fixes * Log file can now be specified at any location * VST 2.x : allow host callback functions to be called during plugin initialization ## 0.7.0 * Multicore track rendering through Twine library * Processor performance timings statistics * Dummy frontend for investigating RT safety issues in plugins ## 0.6.2 * Fix RASPA plugin initialization issue * Fix parameter output from VST 2.x plugins ## 0.6.1 * Fix: handling of track pan for multichannel plugins * Fix: erroneous channel handling for offline frontend ## 0.6 * Multichannel track/buses architecture and support for VST multichannel plugins * Handling of events and MIDI outputs from plugins * Time information handling * Scheduling of asynchronous tasks from internal plugins * Arpeggiator and peak meter internal plugins * Many fixes and improvements to RASPA frontend and VST wrappers ## 0.5.1 * RASPA Xenomai frontend * Multichannel I/O from audio frontends ## 0.5 * VST 3.6 wrapper * Dynamic plugin loading ## 0.4 * VST 2.4 wrapper * Xenomai offline frontend * More advanced MIDI routing and mapping ## 0.3 * JACK audio frontend * MIDI support * OSC control ## 0.2 * Events handling * Internal plugins (gain, eq, sampler) ## 0.1 * Initial version * Offline frontend with I/O with libsndfile ================================================ FILE: LICENSE.md ================================================ #Licensing information All SUSHI code is Copyright © 2017-2023 [Elk Audio AB], formerly [Modern Ancient Instruments Networked AB] (“Elk”) and is licensed under the [https://www.gnu.org/licenses/agpl-3.0.en.html](GNU Affero General Public License v.3) (“AGPLv3”). Such SUSHI code is considered the “Program” within the meaning of the AGPLv3. Loading plugins or libraries into the Program, or statically or dynamically linking or interfacing other applications, plugins, or libraries with the Program, including through a network API, is making a combined work based on the Program. Thus, the AGPLv3 covers the entire combination. You may therefore propagate, convey, or make available over a network, this combined work only in full compliance with the AGPLv3, including compliance with the obligation to provide the Corresponding Source (as that term is defined and used in the AGPLv3) for this entire combination. As a special exception, Elk gives you permission to combine the Program with plugins, libraries or applications that are either: * made available by you under the GNU General Public License v2 or v3, or the GNU Lesser General Public License v2 or v3; or * made available by you without charge or royalty under other open source or proprietary terms, regardless of whether such terms conflict or are incompatible with the AGPLv3, but only if you make available to any requesting party the Corresponding Source of such plugins, libraries, or applications without charge under license terms that permit commercial royalty-free use, modification and distribution. This exception is an additional permission under section 7 of the AGPLv3. #Additional Notes The Elk name, design, and logo are trademarks or registered trademarks of Elk Audio AB. No permission to use is granted, except that you may use “[for Elk Audio OS]” for promotion of your plugin and “[Elk Powered]” for redistributions of modified versions. For any other uses, please contact info@elk.audio. ================================================ FILE: README.md ================================================ # SUSHI Headless plugin host for ELK Audio OS. ## Sushi as Library Sushi can be used as a standalone terminal application, and can now also be used as a library. For more information on the latter, refer to the [LIBRARY.md] (docs/LIBRARY.md) file in this repository. ## Usage The Sushi standalone project, and consequently the produced binary, reside in the `./apps/` sub-folder of this repository. See `sushi -h` for a complete list of options. Common use cases are: Test in offline mode with I/O from audio file: $ sushi -o -i input_file.wav -c config_file.json Use Core Audio on macOS for realtime audio, with the default devices: $ sushi --coreaudio -c config_file.json Use JACK for realtime audio: $ sushi -j -c config_file.json With JACK, Sushi creates 8 virtual input and output ports that you can connect to other programs or system outputs. ## Sushi macOS Since version 1.0, Sushi can be built natively for macOS as a native binary with all the dependencies statically linked to it. There is a new Core Audio frontend (selectable with the `--coreaudio` command-line option) to interface directly with Core Audio. As an alternative, a Portaudio frontend is also available (with the `--portaudio` flag). With Core Audio, you can select other devices than the default with the `--audio-input-device-uid` and `--audio-output-device-uid` options. To find out the right number there, you can launch Sushi with the `--dump-audio-devices` together with `--coreaudio` to get a list in JSON format printed to stdout. MIDI support is provided through RtMidi and can access CoreMidi devices directly. LV2 support is currently not available for macOS. ## Sushi Windows Since version 1.2, Sushi can be built natively for Windows. The suggested audio frontend for Windows is Portaudio (selectable with the `--portaudio` option), which can use most available sound device apis on Windows. You can select which devices to use with the `--audio-input-device` and `--audio-output-device` options. As with Core Audio, to find the id corresponding to a device, launch Sushi with the `--dump-audio-devices` option together with `--portaudio` to get a JSON formatted list printed to stdout. By default, Portaudio does not support Asio, to build with Asio support, pass the `-DPA_USE_ASIO=ON` flag to cmake. This will download and build the Asio SDK automatically. LV2 support is currently not available for Windows. ## Example Sushi configuration files in repository Under `misc/config_files` in this repository, we have a large variety of example Sushi configuration files. The first one to try to check if everything is running properly, would be this one that uses the internal sequencer & synthesizer to generate a sequence: ``` $ ./sushi --coreaudio -c config_files/play_brickworks_synth.json ``` (on Linux with JACK, replace `--coreaudio` with `--jack`). Many of the examples use the mda-vst3 plugins which are built when building Sushi. If you are running one of the prebuilt packages (available on the releases sections on GitHub), you have everything inside the `sushi` folder there. For example, on macOS you should be able to get a simple working synthesizer with: Otherwise, if you are building from source, the plugins used by the examples can be found under: `build/debug/VST3/Debug`, or `build/release/VST3/Release` respectively, for debug and release builds. To run Sushi with an example configuration, you simply invoke it while pointing to one of the above paths. On Ubuntu that could be: ``` $ ./sushi -j -c ../../misc/config_files/play_vst3.json --base-plugin-path VST3/Debug ``` Or, from a macOS terminal: ``` $ ./sushi --coreaudio -c ../../misc/config_files/play_vst3.json --base-plugin-path VST3/Release ``` ## Extra documentation Configuration files are used for global host configs, track and plugins configuration, MIDI routing and mapping, events sequencing. More in-depth documentation is available at the [Elk Audio OS official docs page](https://elk-audio.github.io/elk-docs/html/sushi/index.html). ## Building Sushi builds are supported for native Linux systems, Yocto/OE cross-compiling toolchains targeting Elk Audio OS systems, and macOS. Make sure that Sushi is cloned with the `--recursive` flag to fetch all required submodules for building. Alternatively run `git submodule update --init --recursive` after cloning. Sushi requires a compiler with support for C++17 features. The recommended compilers are GCC version 10 or higher, and clang version 13 or higher. ### Native Linux dependencies Sushi handles most dependencies with vcpkg (or as submodules) and will build and link with them automatically. A few dependencies are not included however and must be provided or installed system-wide. See the list below (debian packages names): * libasound2-dev * libjack-jackd2-dev * For VST 2: * Vst 2.4 SDK - Needs to be provided externally as it is not available from Steinberg anymore. * For LV2: * liblilv-dev - at least version 0.24.4. Lilv is an official wrapper for LV2. * lilv-utils - at least version 0.24.5. * lv2-dev - at least version 1.18.2. The main LV2 library. The official Ubuntu repositories do not have these latest versions at the time of writing. The best source for them is instead the [KX Studio repositories, which you need to enable manually](https://kx.studio/Repositories). * For LV2 unit tests: * lv2-examples - at least version 1.18.2. * mda-lv2 - at least version 1.2.4 of [drobilla's port](http://drobilla.net/software/mda-lv2/) - not that from Mod Devices or others. ### Building with vcpkg (native Linux & macOS) To build from a terminal, use the commands below. Vcpkg also integrates nicely with CLion and other IDEs though must manually set the toolchain file. ``` $ mkdir build && cd build $ ../third-party/vcpkg/bootstrap-vcpkg.sh $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../third-party/vcpkg/scripts/buildsystems/vcpkg.cmake .. $ make ``` This might take a while the first time since all the vcpkg dependencies will have to be built first. **Note:** if using Cmake 4 on MacOS, you might need to set the environment variable SDKROOT to *macosx* in order to build the vcpkg dependencies correctly: ``` SDKROOT=macosx cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../third-party/vcpkg/scripts/buildsystems/vcpkg.cmake .. ``` ### Building with vcpkg (Windows) Building on Windows is similar to Posix and macOS, with the addition of the triplet configuration. To build from a terminal, use the commands below. Vcpkg also integrates nicely with CLion and Visual Studio, though you must manually set the toolchain file for vcpkg integration. ```` $ mkdir build ; cd build $ ../third-party/vcpkg/bootstrap-vcpkg.bat $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="../third-party/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_TRIPLETS="../triplets/" -DVCPKG_TARGET_TRIPLET=win-custom-x86-64 .. $ cmake --build ./ --config Release ```` ### Building with Yocto for Elk Audio OS Sushi can be built either with the provided [Elk Audio OS SDK](https://github.com/elk-audio/elkpi-sdk), or as part of a [full Elk Audio OS image build with bitbake](https://github.com/elk-audio/elk-audio-os-builder). Follow the instructions in those repositories to set up a cross-compiling SDK and build Sushi for a given target. ### Useful CMake build options Option | Value | Notes --------------------------------------|----------|------------------------------------------------------------------------------------------------------ SUSHI_AUDIO_BUFFER_SIZE | 8 - 512 | The buffer size used in the audio processing. Needs to be a power of 2 (8, 16, 32, 64, 128...). SUSHI_WITH_RASPA | on / off | Build Sushi with Xenomai RT-kernel support, only for ElkPowered hardware. SUSHI_WITH_JACK | on / off | Build Sushi with Jack Audio support, only for standard Linux distributions and macOS. SUSHI_WITH_PORTAUDIO | on / off | Build Sushi with Portaudio support. SUSHI_WITH_APPLE_COREAUDIO | on / off | Build Sushi with Apple Core Audio support. SUSHI_WITH_ALSA_MIDI | on / off | Build Sushi with Alsa sequencer support for MIDI (Linux only). SUSHI_WITH_RT_MIDI | on / off | Build Sushi with RtMidi support for MIDI. Cannot be selected if SUSHI_WITH_ALSA_MIDI is set. SUSHI_WITH_LINK | on / off | Build Sushi with Ableton Link support. SUSHI_WITH_VST2 | on / off | Include support for loading Vst 2.x plugins in Sushi. SUSHI_WITH_VST3 | on / off | Include support for loading Vst 3.x plugins in Sushi. SUSHI_LINK_VST3 | on / off | Link Vst3 SDK (statically) if Vst3 is included. Turn this off if Sushi is linked statically as a library, into an application which already links Vst3's SDK. SUSHI_WITH_LV2 | on / off | Include support for loading LV2 plugins in Sushi. SUSHI_WITH_RPC_INTERFACE | on / off | Build gRPC external control interface, requires gRPC development files. SUSHI_BUILD_TWINE | on / off | Build and link with the included version of [TWINE](https://github.com/elk-audio/twine), otherwise tries to link with system wide if the option is disabled. SUSHI_TWINE_STATIC | on / off | Link statically against TWINE (not recommended, useful only in a few cases). SUSHI_WITH_UNIT_TESTS | on / off | Build and run unit tests together with building Sushi. SUSHI_WITH_LV2_MDA_TESTS | on / off | Include LV2 unit tests which depends on the LV2 drobilla port of the mda plugins being installed. SUSHI_VST2_SDK_PATH | path | Path to external Vst 2.4 SDK. Not included and required if WITH_VST2 is enabled. SUSHI_WITH_SENTRY | on / off | Build Sushi with Sentry error logging support. SUSHI_SENTRY_DSN | url | URL to the default value for the Sushi Sentry logging DSN. This can still be passed as a runtime terminal argument. SUSHI_DISABLE_MULTICORE_UNIT_TESTS | on / off | Disable unit-tests dependent on multi-core processing. SUSHI_BUILD_STANDALONE_APP | on / off | Build standalone Sushi executable. SUSHI_BUILD_WITH_SANITIZERS | on / off | Build Sushi with google address sanitizer on. Default is off. The default values for many of the options are platform-specific (native Linux, Yocto/OE, macOS). _Note_: before version 1.0, the CMake options didn't have the `SUSHI_` prefix. The old names (e.g. `WITH_JACK`) are not supported anymore and should be changed to the new format. ## Running Unit tests separately Some Sushi's unit tests depend on test data, which is found through the environment variable `SUSHI_TEST_DATA_DIR`. You will need to define this if you want to run the unit test explicitly, e.g. while debugging: `$ export SUSHI_TEST_DATA_DIR=/path/to/sushi/repo/test/data` Moreover, the battery of tests for VST3 plugin support, build and use the example plugins of the VST3 SDK. While the automated tests running as part of the build find these plugins automatically, when running the `unit_tests` binary manually, you may need to set the working directory to: `/path/to/sushi/build_folder_name/test`, for the path to resolve correctly. ## License Sushi is licensed under Affero General Public License (“AGPLv3”). See [LICENSE](LICENSE.md) document for the full details of the license. For contributing code to Sushi, see [CONTRIBUTING.md](CONTRIBUTING.md). Copyright 2017-2023 Elk Audio AB, Stockholm, Sweden. ================================================ FILE: apps/CMakeLists.txt ================================================ ###################### # Executable target # ###################### set(APP_FILES main.cpp) if(APPLE) add_executable(sushi MACOSX_BUNDLE ${APP_FILES}) else() add_executable(sushi ${APP_FILES}) endif() #################################### # Compiler Flags and definitions # #################################### if(MSVC) # C4996: Deprecated warning. This was flooding the terminal, # I might remove the suppression from here eventually, # and out into the code again. set(SUSHI_COMPILE_OPTIONS /W4 /fp:fast /wd4996) set(APP_LINK_LIBRARIES ${APP_LINK_LIBRARIES} winmm.lib) # oscpack GetCurrentTimeMs else() set(SUSHI_COMPILE_OPTIONS -Wall -Wextra -Wno-psabi -fPIC -fno-rtti -ffast-math) endif() target_compile_options(sushi PRIVATE ${SUSHI_COMPILE_OPTIONS}) target_link_libraries(sushi PRIVATE sushi_library ${APP_LINK_LIBRARIES}) #################### # Install # #################### set(DOC_FILES_INSTALL "${PROJECT_SOURCE_DIR}/LICENSE.md" "${PROJECT_SOURCE_DIR}/README.md" "${PROJECT_SOURCE_DIR}/HISTORY.md" ) if (APPLE) # TODO: fix to work when not submodule install(CODE [[ include(BundleUtilities) fixup_bundle("${CMAKE_INSTALL_PREFIX}/sushi/sushi.app" "" "") ]] COMPONENT Runtime) endif() ### Custom command to copy the dynamic dependencies to the binary folder # Mainly for twine.dll because windows cannot find it from ../twine but # needs it in the same directory if (MSVC) add_custom_command(TARGET sushi POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS) endif() install(TARGETS sushi RUNTIME DESTINATION bin BUNDLE DESTINATION Applications) foreach(ITEM ${DOC_FILES_INSTALL}) install(FILES ${ITEM} DESTINATION share/sushi/doc) endforeach() ================================================ FILE: apps/main.cpp ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Main entry point to Sushi * @copyright 2017-2023 Elk Audio AB, Stockholm */ #include #include #include #include "elklog/static_logger.h" #include "sushi/utils.h" #include "sushi/parameter_dump.h" #include "sushi/portaudio_devices_dump.h" #include "sushi/coreaudio_devices_dump.h" #include "sushi/sushi.h" #include "sushi/terminal_utilities.h" #include "sushi/standalone_factory.h" #include "sushi/offline_factory.h" using namespace sushi; ELKLOG_GET_LOGGER_WITH_MODULE_NAME("main"); /** * These are used for signalling that Sushi should exit, from across threads. * In main(), Sushi waits on this condition variable to exit. */ bool exit_flag = false; std::condition_variable exit_notifier; bool exit_condition() { return exit_flag; } /** * By invoking this method, you can signal to Sushi to exit, * either through passing it to the standard signal(...) method, * or through calling it directly in the code, e.g. when coming upon an unrecoverable error. * When invoking this, Sushi will still wind down, cleanly close allocated resources, and flush logs. * @param sig the signal, e.g. SIGINT, SIGTERM. */ void exit_on_signal([[maybe_unused]] int sig) { exit_flag = true; exit_notifier.notify_one(); } /** * If the error encountered is so severe as to require immediate exit, invoke this, instead of exit_on_signal. * @param message */ void error_exit(const std::string& message, sushi::Status status) { std::cerr << message << std::endl; int error_code = static_cast(status); std::exit(error_code); } /** * Tries to start Sushi * @param options a SushiOptions structure * @return a Sushi instance, if successful - if not, the unique_ptr is empty. */ std::unique_ptr start_sushi(SushiOptions options); void pipe_signal_handler([[maybe_unused]] int sig) { ELKLOG_LOG_INFO("Pipe signal received and ignored: {}", sig); } static void print_sushi_headline() { std::cout << "SUSHI - Copyright 2017-2023 Elk Audio AB, Stockholm" << std::endl; std::cout << "SUSHI is licensed under the Affero GPL 3.0. Source code is available at github.com/elk-audio" << std::endl; } int main(int argc, char* argv[]) { signal(SIGINT, exit_on_signal); signal(SIGTERM, exit_on_signal); #ifndef _MSC_VER signal(SIGPIPE, pipe_signal_handler); #endif // option_parser accepts arguments excluding program name, // so skip it if it is present. if (argc > 0) { argc--; argv++; } SushiOptions options; options.config_source = sushi::ConfigurationSource::FILE; auto option_status = parse_options(argc, argv, options); if (option_status == ParseStatus::ERROR) { return 1; } else if (option_status == ParseStatus::MISSING_ARGUMENTS) { return 2; } else if (option_status == ParseStatus::EXIT) { return 0; } init_logger(options); if (options.enable_audio_devices_dump) { if (options.frontend_type == FrontendType::PORTAUDIO) { #ifdef SUSHI_BUILD_WITH_PORTAUDIO std::cout << sushi::generate_portaudio_devices_info_document() << std::endl; return 0; #else std::cerr << "SUSHI not built with Portaudio support, cannot dump devices." << std::endl; #endif } else if (options.frontend_type == FrontendType::APPLE_COREAUDIO) { #ifdef SUSHI_BUILD_WITH_APPLE_COREAUDIO std::cout << sushi::generate_coreaudio_devices_info_document() << std::endl; #else std::cerr << "SUSHI not built with Apple CoreAudio support, cannot dump devices." << std::endl; #endif return 0; } else { std::cout << "No frontend specified or specified frontend not supported (please specify ." << std::endl; return 1; } } auto sushi = start_sushi(options); if (sushi == nullptr) { return -1; } if (options.frontend_type != FrontendType::OFFLINE) { std::mutex m; std::unique_lock lock(m); exit_notifier.wait(lock, exit_condition); } sushi->stop(); ELKLOG_LOG_INFO("Sushi exiting normally!"); return 0; } std::unique_ptr start_sushi(SushiOptions options) { std::unique_ptr factory; if (options.frontend_type == FrontendType::DUMMY || options.frontend_type == FrontendType::OFFLINE) { factory = std::make_unique(); } else if (options.frontend_type == FrontendType::JACK || options.frontend_type == FrontendType::XENOMAI_RASPA || options.frontend_type == FrontendType::APPLE_COREAUDIO || options.frontend_type == FrontendType::PORTAUDIO) { factory = std::make_unique(); } else { error_exit("Invalid frontend configuration. Reactive, or None, are not supported when standalone.", Status::FRONTEND_IS_INCOMPATIBLE_WITH_STANDALONE); } // Initialising: auto [sushi, status] = factory->new_instance(options); if (status == Status::FAILED_OSC_FRONTEND_INITIALIZATION) { error_exit("Instantiating OSC server on port " + std::to_string(options.osc_server_port) + " failed.", status); } else if (status != Status::OK) { auto message = to_string(status); if (status == Status::FAILED_INVALID_FILE_PATH) { message.append(options.config_filename); } error_exit(message, status); } if (options.enable_parameter_dump) { std::cout << sushi::generate_processor_parameter_document(sushi->controller()); std::cout << "Parameter dump completed - exiting." << std::endl; std::exit(EXIT_SUCCESS); } else { print_sushi_headline(); } // ...and starting: auto start_status = sushi->start(); if (start_status == Status::OK) { return std::move(sushi); } else if (start_status == Status::FAILED_TO_START_RPC_SERVER) { sushi.reset(); error_exit("Failure starting gRPC server on address " + options.grpc_listening_address, status); } return nullptr; } ================================================ FILE: bitbucket-pipelines.yml ================================================ # Only use spaces to indent your .yml configuration. # ----- # You can specify a custom docker image from Docker Hub as your build environment. image: ubuntu:24.04 pipelines: default: - step: name: Build sushi + tests size: 4x caches: - vcpkg script: # Prepare system. - apt-get update -y - apt install build-essential -y - apt-get install -y -q libjack-jackd2-dev liblo-dev - apt-get install -y -q libsndfile1-dev libasound2-dev - apt-get install -y -q cmake git make - apt-get install -y -q curl zip unzip tar - apt-get install -y -q libgrpc++-dev protobuf-compiler-grpc - apt-get install -y -q liblilv-dev lilv-utils lv2-dev lv2-examples mda-lv2 - apt-get install -y software-properties-common - add-apt-repository -y ppa:ubuntu-toolchain-r/test - export LV2_PATH=$HOME/.lv2:/usr/local/lib/lv2:/usr/lib/lv2 # Prepare dependencies # Rewrite the relative submodule url as bitbucket doesn't handle relative paths correctly - sed -i 's/url = \.\.\(.*\)/url = git@bitbucket.org:'$BITBUCKET_TEAM'\1/g' .gitmodules - git submodule update --init --recursive # Set test data dir (for running unit test manually) - export SUSHI_TEST_DATA_DIR=$PWD/test/data # Set the number of cores to use when building - export BUILD_CORES=8 # Boostrap vcpkg - export VCPKG_BINARY_SOURCES="clear;files,$(pwd)/vcpkgcache,readwrite" - ./third-party/vcpkg/bootstrap-vcpkg.sh - mkdir build - cd build - cmake -DCMAKE_TOOLCHAIN_FILE=../third-party/vcpkg/scripts/buildsystems/vcpkg.cmake -DWITH_XENOMAI=FALSE -DWITH_JACK=TRUE -DWITH_VST2=OFF -DSUSHI_WITH_PORTAUDIO=ON -DWITH_LV2=ON -DWITH_LV2_MDA_TESTS=ON -DTWINE_WITH_XENOMAI=OFF -DSUSHI_BUILD_WITH_SANITIZERS=OFF .. # Build multiple configuration and run unit tests # Build sushi and unit tests with default options and default buffer size (64 samples) - make sushi -j${BUILD_CORES} - make unit_tests -j${BUILD_CORES} # Run unit tests with some tests test disabled - cd test - ./unit_tests --gtest_filter=-TestAudioGraph.TestMultiCoreOperation:TestLv2Wrapper.TestSynchronousStateAndWorkerThreads:TestLv2Wrapper.TestMidiEventInputAndOutput - cd .. # Build with buffers size 32 samples - cmake -DSUSHI_AUDIO_BUFFER_SIZE=32 .. - make sushi -j${BUILD_CORES} - make unit_tests -j${BUILD_CORES} - cd test - ./unit_tests --gtest_filter=-TestAudioGraph.TestMultiCoreOperation:TestLv2Wrapper.TestSynchronousStateAndWorkerThreads:TestLv2Wrapper.TestMidiEventInputAndOutput - cd .. # Build with buffer size 128 samples - cmake -DSUSHI_AUDIO_BUFFER_SIZE=128 .. - make sushi -j${BUILD_CORES} - make unit_tests -j${BUILD_CORES} - cd test - ./unit_tests --gtest_filter=-TestAudioGraph.TestMultiCoreOperation:TestLv2Wrapper.TestSynchronousStateAndWorkerThreads:TestLv2Wrapper.TestMidiEventInputAndOutput - cd .. # Quick smoke test (not really working yet) #- ./sushi -d -c ../../misc/config_files/config_play.json& #- sleep 6 #- pkill sushi # Build with most options disabled to test compile time configuration - cmake -DAUDIO_BUFFER_SIZE=64 -DSUSHI_WITH_VST2=OFF -DSUSHI_WITH_VST3=OFF -DSUSHI_WITH_LV2=OFF -DSUSHI_WITH_LV2_MDA_TESTS=OFF -DSUSHI_WITH_RT_MIDI=ON -DSUSHI_WITH_ALSA_MIDI=OFF -DSUSHI_WITH_PORTAUDIO=OFF .. - make sushi -j4 - make unit_tests -j${BUILD_CORES} - cd test - ./unit_tests --gtest_filter=-TestAudioGraph.TestMultiCoreOperation - cd .. definitions: caches: vcpkg: vcpkgcache ================================================ FILE: docs/LIBRARY.md ================================================ # Sushi as a Library Using Sushi as a statically linked library is a newly developed feature. We have considered two primary use-cases for Sushi as a library: **Reactive:** Sushi running inside a plugin / other audio host, which provides the audio and MIDI frontends, passing on audio and MIDI to Sushi. **Active:** Allowing the creation of standalone audio-hosts, which embed Sushi, but expand on the functionality of the terminal application we provide in `/apps`. For the Reactive use-case, we have developed new frontends for audio and MIDI in Sushi, designed to be Reactive, as well as a new "Real-Time" Controller interface using which the necessary Sushi control is available in the audio callback. These are still in their first incarnation, and while they are definitely usable in production, they are currently missing features. That’s also why these files may still contain “TODO’s” as placeholders - to be removed as the features they refer to are added. For the Active use-case, there are no such limitations - the full Sushi functionality is available. ## The main limitations for the Reactive use-case are currently that: * Sushi can only work with stereo audio I/O. * Sushi's audio buffer size is set at compile time, using the CMake argument SUSHI_AUDIO_BUFFER_SIZE. But an audio host may have a dynamic buffer-size setting. If the buffer size doesn't match, the host needs to handle that, ensuring Sushi is only ever given audio buffers of the size defined at build-time. * MIDI I/O to Sushi from a containing host application is not currently real-time, but asynchronous, meaning MIDI and audio synchronisation is not sample-accurate. * For any control commands to be processes by Sushi, it needs to be receiving audio buffers regularly. When no audio callbacks are received, Sushi will also not process and control commands (e.g. those received over gRPC and OSC). ## Using Sushi as a library For using Sushi as a library, you only need to be aware of the header files contained in the include/sushi folder, where Sushi's API is exposed. The internals are hidden away. Some of those headers are useful only when Sushi is Passive, others only when it's Active, and others for both cases. ## Common API for Reactive and Active Sushi has several compile-time arguments and dependencies, which apply equally to the library and standalone use. These are documented in the main [README.md](../README.md) of this repository, and will not be repeated here. ### Instantiation The main Sushi API is defined in sushi.h. All options for instantiating Sushi are collected in the struct **SushiOptions**. Some of its fields are enum classes - which are then also defined in sushi.h. This file also contains the main abstract base-class for Sushi, which however cannot be instantiated directly. Instead, one of the provided factories needs to be used. Simply create a factory, and invoke the below method, passing your populated SushiOptions struct: ```c++ std::pair, Status> new_instance(SushiOptions& options) override; ``` It will return a std::pair, of either a valid Sushi instance, and Status::OK, or an empty std::unique_ptr, and the Status of why instantiation failed. Note that the SushiOptions is passed by reference to new_instance, because its contents are *suggestions*, which may be overridden if needed - in which case your options struct instance will be updated to reflect the changes made. ### Controlling during run-time #### C++ Control API The main control interface is exposed in **control_interface.h**. Note that none of those classes can be instantiated directly. They are created internally in Sushi, and their instances are accessed through the **Sushi::Controller()** method, which returns a pointer to a **SushiControl** instance, that in turn will hold instances for all the interfaces defined in control_interface.h: ```C++ control::SushiControl* controller(); ``` It's important to note that none of these interfaces are real-time safe. That means they are not safely callable from within an audio-callback, but should be invoked from a separate thread altogether. Sushi then schedules their effect internally so that they do not interfere with the real-time audio callback. The C++ control API in control_interface.h is extensive and will not be further documented here. It is written so that it should be intuitively self-explanatory. #### Remote Control API Additionally, Sushi can be controlled over gRPC and OSC, which are both unchanged in the "sushi as library" implementation - see the documentation for this in the main [README.md](../README.md) of this repository, and the [Elk Audio GitHub page with detaled documentation](https://elk-audio.github.io/elk-docs/html/index.html). Once Sushi is instantiated, subsequent steps vary depending on the use-case. ## Reactive use-case You will need the following fields: ```C++ sushi::SushiOptions _sushi_options; std::unique_ptr _rt_controller; std::unique_ptr _sushi; sushi::ChunkSampleBuffer _sushi_buffer_in; sushi::ChunkSampleBuffer _sushi_buffer_out; ``` ### Starting Reactive Sushi For the Reactive use-case, fetch the **RtController** from a **ReactiveFactory** instance (Sushi factories are single-use). Assuming Sushi has started successfully, this will be a non-null std::unique_ptr, meaning you are taking over the ownership of the controller. RtController is named as such because its methods are safe to invoke during a real-time audio callback. ```C++ sushi::ReactiveFactory factory; auto [sushi, status] = factory.new_instance(_sushi_options); _sushi = std::move (sushi); _rt_controller = factory.rt_controller(); ``` If you are going to use MIDI, pass a callback Lambda to it through your RtController instance, so that Sushi can output MIDI data to your host: ```C++ _rt_controller->set_midi_callback( [&] (int output, sushi::MidiDataByte data, sushi::Time /*timestamp*/) { // Handle MIDI data. } ); ``` If you're going to host Sushi in e.g. an audio plugin, which can pass a transport position to Sushi, you need to tell Sushi to not calculate its own transport position internally: ```C++ _rt_controller->set_position_source (sushi::TransportPositionSource::EXTERNAL); ``` Finally, start Sushi, and if successful, set the sample rate. If not, address any issue and retry: ```c++ _sushi_start_status = _sushi->start(); if (_sushi_start_status == sushi::Status::OK) { _sushi->set_sample_rate(_sample_rate); return true; // Sushi is started. } else { _sushi = nullptr; _rt_controller = nullptr; // The factory is single use. Address the error, and perhaps retry, with a new factory instance. } ``` For example: If you are using gRPC, it is possible that Sushi failed to start because the configured gRPC port was taken. You can then increment the port number using the utility method provided, and retry. ```C++ bool incrementation_status = _sushi_options.increment_grpc_port_number(); if (!incrementation_status) { // Starting Sushi failed: gRPC address is malformed. break; } ``` ### Invoking Sushi in the audio callback of your host For each audio callback invocation, you need to do the following, always using the RtController instance. Pass MIDI to Sushi using receive_midi. ```C++ void receive_midi(int input, MidiDataByte data, Time timestamp); ``` If you're using an external transport position source: update the Sushi transport, using the following. ```C++ void set_tempo(float tempo); void set_time_signature(control::TimeSignature time_signature); void set_playing_mode(control::PlayingMode mode); bool set_current_beats(double beat_time); bool set_current_bar_beats(double bar_beat_count); ``` Fill your sushi::**ChunkSampleBuffer** input audio buffer, and call process_audio on the RtController. ```C++ void process_audio(ChunkSampleBuffer& in_buffer, ChunkSampleBuffer& out_buffer, Time timestamp) ``` This will populate the out_buffer. The sushi::**Time** `timestamp` may either be calculated using your hosts playhead data, or you need to use the RtController utility to calculate it, if you're using the Sushi internal transport. ```C++ sushi::Time calculate_timestamp_from_start(float sample_rate) const ``` In which case, after the process_audio call, you will then also need to increment_samples_since_start. ```C++ void increment_samples_since_start(uint64_t sample_count, Time timestamp) ``` There is more detail to the above process to handle e.g. MIDI, or non-matching buffer sizes. We will be publishing a concrete example, showing how to implement a concrete audio plugin which wraps Sushi, in due course. ### Stopping Reactive Sushi To stop Sushi, you'll really only need to delete the controller, call stop() on your sushi instance, and then delete that too: ```C++ if (_sushi) { _rt_controller.reset(); _sushi->stop(); _sushi.reset(); } ``` ## Active use-case For the active use-case the Sushi API footprint is a bit smaller. You will need the following fields. ```c++ SushiOptions options; std::unique_ptr factory; std::unique_ptr sushi; ``` Then depending on whether you plan to use an `OFFLINE` frontend, or if you want Sushi to instantiate its own Audio frontend (the options are defined in sushi::FrontendType), you will instantiate either a **StandaloneFactory**. ```C++ factory = std::make_unique(); ``` Or an **OfflineFactory**. ```C++ factory = std::make_unique(); ``` To populate SushiOptions, we provide a utility method for parsing options from the Sushi terminal arguments, in **terminal_utilities.h**. ```C++ ParseStatus parse_options(int argc, char* argv[], sushi::SushiOptions& options); ``` From there on, you instantiate Sushi. ```C++ auto [sushi, status] = factory->new_instance(options); ``` And on success, start it. ```C++ auto start_status = sushi->start(); ``` From there on, you can wait in the creating thread, until the application needs to exit, at which point you just need to invoke stop(). ```C++ sushi->stop(); ``` ## Examples For examples of sushi as a library: ### Active use-case See apps/main.cpp in this repository, which shows a terminal application use-case. ### Passive use-case See the example JUCE-based project, residing in a dedicated repository, where we demonstrate how Sushi can be wrapped in a JUCE audio plugin. ================================================ FILE: include/sushi/compile_time_settings.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Methods for querying compile - time configuration (build options, version, githash, etc). * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_COMPILE_TIME_SETTINGS_H #define SUSHI_COMPILE_TIME_SETTINGS_H #include #include #include "options.h" #include "generated/version.h" // For AUDIO_CHUNK_SIZE. #include "constants.h" namespace sushi { struct CompileTimeSettings { static constexpr auto sushi_version = SUSHI_STRINGIZE(SUSHI__VERSION_MAJ) "." SUSHI_STRINGIZE(SUSHI__VERSION_MIN) "." SUSHI_STRINGIZE(SUSHI__VERSION_REV); static constexpr auto sushi_api_version = SUSHI_EXTERNAL_API_VERSION; static constexpr auto git_commit_hash = SUSHI_GIT_COMMIT_HASH; static constexpr auto build_timestamp = SUSHI_BUILD_TIMESTAMP; static constexpr auto audio_chunk_size = AUDIO_CHUNK_SIZE; static constexpr std::array enabled_build_options = { #ifdef SUSHI_BUILD_WITH_VST2 "vst2", #endif #ifdef SUSHI_BUILD_WITH_VST3 "vst3", #endif #ifdef SUSHI_BUILD_WITH_LV2 "lv2", #endif #ifdef SUSHI_BUILD_WITH_JACK "jack", #endif #ifdef SUSHI_BUILD_WITH_RASPA "raspa", #endif #ifdef SUSHI_BUILD_WITH_RPC_INTERFACE "rpc control", #endif #ifdef SUSHI_BUILD_WITH_ABLETON_LINK "ableton link", #endif #ifdef SUSHI_BUILD_WITH_SENTRY "sentry" #endif // Without this entry, if all ifdefs are false, compiling will fail. // Not as uncommon as you may think: building without LV2, VST2 and VST3 causes this for unit-tests. #if !defined (SUSHI_BUILD_WITH_VST2) \ && !defined (SUSHI_BUILD_WITH_VST3) \ && !defined (SUSHI_BUILD_WITH_LV2) \ && !defined (SUSHI_BUILD_WITH_JACK) \ && !defined (SUSHI_BUILD_WITH_RASPA)\ && !defined (SUSHI_BUILD_WITH_RPC_INTERFACE) \ && !defined (SUSHI_BUILD_WITH_ABLETON_LINK) \ && !defined (SUSHI_BUILD_WITH_SENTRY) "" #endif }; }; } // end namespace sushi #endif // SUSHI_COMPILE_TIME_SETTINGS_H ================================================ FILE: include/sushi/constants.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Compile time constants * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #include #ifndef SUSHI_CONSTANTS_H #define SUSHI_CONSTANTS_H namespace sushi { /* The number of samples to process in one chunk. It is defined as a compile-time constant to give more room for optimizations */ #ifdef SUSHI_CUSTOM_AUDIO_CHUNK_SIZE constexpr int AUDIO_CHUNK_SIZE = SUSHI_CUSTOM_AUDIO_CHUNK_SIZE; #else constexpr int AUDIO_CHUNK_SIZE = 64; #endif constexpr int MAX_ENGINE_CV_IO_PORTS = 4; constexpr int MAX_ENGINE_GATE_PORTS = 8; constexpr int MAX_ENGINE_GATE_NOTE_NO = 127; constexpr int MAX_TRACK_CHANNELS = 16; constexpr float PAN_GAIN_3_DB = 1.412537f; constexpr auto GAIN_SMOOTHING_TIME = std::chrono::milliseconds(20); constexpr int SUSHI_PPQN_TICK = 24; /* Use in class declaration to disallow copying of this class. * Note that this marks copy constructor and assignment operator * as deleted and hence their r-value counterparts are not generated. * * In order to make a class moveable though still non-copyable, * implement a move constructor and move assignment operator. Default * copy constructor will then not be generated. Usage of this macro is * in this case not necessary to make the class non-copyable. But can * still be used for clarity. */ #define SUSHI_DECLARE_NON_COPYABLE(type) type(const type& other) = delete; \ type& operator=(const type&) = delete; } // end namespace sushi #endif //SUSHI_CONSTANTS_H ================================================ FILE: include/sushi/control_interface.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Abstract interface for external control of sushi over rpc, osc or similar * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_CONTROL_INTERFACE_H #define SUSHI_CONTROL_INTERFACE_H #include #include #include #include #include #include #include "elk-warning-suppressor/warning_suppressor.hpp" #ifdef ERROR #undef ERROR #endif ELK_PUSH_WARNING ELK_DISABLE_TRIVIAL_DESTRUCTOR_NOT_VIRTUAL namespace sushi::control { using Time = std::chrono::microseconds; enum class ControlStatus { OK, ASYNC_RESPONSE, ERROR, UNSUPPORTED_OPERATION, NOT_FOUND, OUT_OF_RANGE, INVALID_ARGUMENTS }; enum class PlayingMode { STOPPED, PLAYING, RECORDING }; enum class SyncMode { INTERNAL, MIDI, GATE, LINK }; struct ControlResponse { ControlStatus status; int id; }; struct TimeSignature { int numerator; int denominator; }; struct Timings { float avg; float min; float max; }; struct CpuTimings { Timings main; std::vector threads; }; enum class PluginType { INTERNAL, VST2X, VST3X, LV2 }; enum class ParameterType { BOOL, INT, FLOAT }; struct ParameterInfo { int id; ParameterType type; std::string label; std::string name; std::string unit; bool automatable; float min_domain_value; float max_domain_value; }; struct PropertyInfo { int id; std::string name; std::string label; }; struct ProcessorInfo { int id; std::string label; std::string name; int parameter_count; int program_count; }; struct ProgramInfo { int id; std::string name; }; enum class TrackType { REGULAR, PRE, POST }; struct TrackInfo { int id; std::string label; std::string name; int channels; int buses; int thread; TrackType type; std::vector processors; }; struct ProcessorState { std::optional bypassed; std::optional program; std::vector> parameters; std::vector> properties; std::vector binary_data; }; struct SushiBuildInfo { std::string version; std::vector build_options; int audio_buffer_size; std::string commit_hash; std::string build_date; }; enum class MidiChannel { MIDI_CH_1, MIDI_CH_2, MIDI_CH_3, MIDI_CH_4, MIDI_CH_5, MIDI_CH_6, MIDI_CH_7, MIDI_CH_8, MIDI_CH_9, MIDI_CH_10, MIDI_CH_11, MIDI_CH_12, MIDI_CH_13, MIDI_CH_14, MIDI_CH_15, MIDI_CH_16, MIDI_CH_OMNI }; struct AudioConnection { int track_id; int track_channel; int engine_channel; }; struct CvConnection { int track_id; int parameter_id; int cv_port_id; }; struct GateConnection { int processor_id; int gate_port_id; int channel; int note_no; }; struct MidiKbdConnection { int track_id; MidiChannel channel; int port; bool raw_midi; }; struct MidiCCConnection { int processor_id; int parameter_id; MidiChannel channel; int port; int cc_number; int min_range; int max_range; bool relative_mode; }; struct MidiPCConnection { int processor_id; MidiChannel channel; int port; }; enum class NotificationType { TRANSPORT_UPDATE, CPU_TIMING_UPDATE, TRACK_UPDATE, PROCESSOR_UPDATE, PARAMETER_CHANGE, PROPERTY_CHANGE, ASYNC_COMMAND_COMPLETION }; enum class ProcessorAction { ADDED, DELETED }; enum class TrackAction { ADDED, DELETED }; enum class TransportAction { PLAYING_MODE_CHANGED, SYNC_MODE_CHANGED, TIME_SIGNATURE_CHANGED, TEMPO_CHANGED }; struct MidiKbdConnectionState { std::string track; MidiChannel channel; int port; bool raw_midi; }; struct MidiCCConnectionState { std::string processor; int parameter_id; MidiChannel channel; int port; int cc_number; float min_range; float max_range; bool relative_mode; }; struct MidiPCConnectionState { std::string processor; MidiChannel channel; int port; }; struct MidiState { int inputs; int outputs; std::vector kbd_input_connections; std::vector kbd_output_connections; std::vector cc_connections; std::vector pc_connections; std::vector enabled_clock_outputs; }; struct OscParameterState { std::string processor; std::vector parameter_ids; }; struct OscState { bool enable_all_processor_outputs; std::vector enabled_processor_outputs; }; struct TrackAudioConnectionState { std::string track; int track_channel; int engine_channel; }; struct EngineState { float sample_rate; float tempo; PlayingMode playing_mode; SyncMode sync_mode; TimeSignature time_signature; bool input_clip_detection; bool output_clip_detection; bool master_limiter; int used_audio_inputs; int used_audio_outputs; std::vector input_connections; std::vector output_connections; }; struct PluginClass { std::string name; std::string label; std::string uid; std::string path; PluginType type; ProcessorState state; }; struct TrackState { std::string name; std::string label; int channels; int buses; int thread; TrackType type; ProcessorState track_state; std::vector processors; }; struct SessionState { SushiBuildInfo sushi_info; std::string save_date; OscState osc_state; MidiState midi_state; EngineState engine_state; std::vector tracks; }; class SystemController { public: virtual ~SystemController() = default; [[nodiscard]] virtual std::string get_sushi_version() const = 0; [[nodiscard]] virtual std::string get_sushi_api_version() const = 0; [[nodiscard]] virtual SushiBuildInfo get_sushi_build_info() const = 0; [[nodiscard]] virtual int get_input_audio_channel_count() const = 0; [[nodiscard]] virtual int get_output_audio_channel_count() const = 0; protected: SystemController() = default; }; class TransportController { public: virtual ~TransportController() = default; [[nodiscard]] virtual float get_samplerate() const = 0; [[nodiscard]] virtual PlayingMode get_playing_mode() const = 0; [[nodiscard]] virtual SyncMode get_sync_mode() const = 0; [[nodiscard]] virtual TimeSignature get_time_signature() const = 0; [[nodiscard]] virtual float get_tempo() const = 0; virtual ControlStatus set_sync_mode(SyncMode sync_mode) = 0; virtual void set_playing_mode(PlayingMode playing_mode) = 0; virtual ControlStatus set_tempo(float tempo) = 0; virtual ControlStatus set_time_signature(TimeSignature signature) = 0; protected: TransportController() = default; }; class TimingController { public: virtual ~TimingController() = default; [[nodiscard]] virtual bool get_timing_statistics_enabled() const = 0; virtual void set_timing_statistics_enabled(bool enabled) = 0; [[nodiscard]] virtual std::pair get_engine_timings() const = 0; [[nodiscard]] virtual std::pair get_track_timings(int track_id) const = 0; [[nodiscard]] virtual std::pair get_processor_timings(int processor_id) const = 0; virtual ControlStatus reset_all_timings() = 0; virtual ControlStatus reset_track_timings(int track_id) = 0; virtual ControlStatus reset_processor_timings(int processor_id) = 0; protected: TimingController() = default; }; class KeyboardController { public: virtual ~KeyboardController() = default; virtual ControlStatus send_note_on(int track_id, int channel, int note, float velocity) = 0; virtual ControlStatus send_note_off(int track_id, int channel, int note, float velocity) = 0; virtual ControlStatus send_note_aftertouch(int track_id, int channel, int note, float value) = 0; virtual ControlStatus send_aftertouch(int track_id, int channel, float value) = 0; virtual ControlStatus send_pitch_bend(int track_id, int channel, float value) = 0; virtual ControlStatus send_modulation(int track_id, int channel, float value) = 0; protected: KeyboardController() = default; }; class AudioGraphController { public: virtual ~AudioGraphController() = default; [[nodiscard]] virtual std::vector get_all_processors() const = 0; [[nodiscard]] virtual std::vector get_all_tracks() const = 0; [[nodiscard]] virtual std::pair get_track_id(const std::string& track_name) const = 0; [[nodiscard]] virtual std::pair get_track_info(int track_id) const = 0; [[nodiscard]] virtual std::pair> get_track_processors(int track_id) const = 0; [[nodiscard]] virtual std::pair get_processor_id(const std::string& processor_name) const = 0; [[nodiscard]] virtual std::pair get_processor_info(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_processor_bypass_state(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_processor_state(int processor_id) const = 0; virtual ControlResponse set_processor_bypass_state(int processor_id, bool bypass_enabled) = 0; virtual ControlResponse set_processor_state(int processor_id, const ProcessorState& state) = 0; virtual ControlResponse create_track(const std::string& name, int channels, std::optional thread) = 0; virtual ControlResponse create_multibus_track(const std::string& name, int buses, std::optional thread) = 0; virtual ControlResponse create_pre_track(const std::string& name) = 0; virtual ControlResponse create_post_track(const std::string& name) = 0; virtual ControlResponse move_processor_on_track(int processor_id, int source_track_id, int dest_track_id, std::optional before_processor_id) = 0; virtual ControlResponse create_processor_on_track(const std::string& name, const std::string& uid, const std::string& file, PluginType type, int track_id, std::optional before_processor_id) = 0; virtual ControlResponse delete_processor_from_track(int processor_id, int track_id) = 0; virtual ControlResponse delete_track(int track_id) = 0; protected: AudioGraphController() = default; }; class ProgramController { public: virtual ~ProgramController() = default; [[nodiscard]] virtual std::pair get_processor_current_program(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_processor_current_program_name(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_processor_program_name(int processor_id, int program_id) const = 0; [[nodiscard]] virtual std::pair> get_processor_programs(int processor_id) const = 0; virtual ControlResponse set_processor_program(int processor_id, int program_id)= 0; protected: ProgramController() = default; }; class ParameterController { public: virtual ~ParameterController() = default; [[nodiscard]] virtual std::pair> get_processor_parameters(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_track_parameters(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_parameter_id(int processor_id, const std::string& parameter) const = 0; [[nodiscard]] virtual std::pair get_parameter_info(int processor_id, int parameter_id) const = 0; [[nodiscard]] virtual std::pair get_parameter_value(int processor_id, int parameter_id) const = 0; [[nodiscard]] virtual std::pair get_parameter_value_in_domain(int processor_id, int parameter_id) const = 0; [[nodiscard]] virtual std::pair get_parameter_value_as_string(int processor_id, int parameter_id) const = 0; virtual ControlStatus set_parameter_value(int processor_id, int parameter_id, float value) = 0; [[nodiscard]] virtual std::pair> get_processor_properties(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_track_properties(int processor_id) const = 0; [[nodiscard]] virtual std::pair get_property_id(int processor_id, const std::string& parameter) const = 0; [[nodiscard]] virtual std::pair get_property_info(int processor_id, int parameter_id) const = 0; [[nodiscard]] virtual std::pair get_property_value(int processor_id, int parameter_id) const = 0; virtual ControlStatus set_property_value(int processor_id, int parameter_id, const std::string& value) = 0; protected: ParameterController() = default; }; class MidiController { public: virtual ~MidiController() = default; [[nodiscard]] virtual int get_input_ports() const = 0; [[nodiscard]] virtual int get_output_ports() const = 0; [[nodiscard]] virtual std::vector get_all_kbd_input_connections() const = 0; [[nodiscard]] virtual std::vector get_all_kbd_output_connections() const = 0; [[nodiscard]] virtual std::vector get_all_cc_input_connections() const = 0; [[nodiscard]] virtual std::vector get_all_pc_input_connections() const = 0; [[nodiscard]] virtual std::pair> get_cc_input_connections_for_processor(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_pc_input_connections_for_processor(int processor_id) const = 0; [[nodiscard]] virtual bool get_midi_clock_output_enabled(int port) const = 0; virtual ControlStatus set_midi_clock_output_enabled(bool enabled, int port) = 0; virtual ControlResponse connect_kbd_input_to_track(int track_id, MidiChannel channel, int port, bool raw_midi) = 0; virtual ControlResponse connect_kbd_output_from_track(int track_id, MidiChannel channel, int port) = 0; virtual ControlResponse connect_cc_to_parameter(int processor_id, int parameter_id, MidiChannel channel, int port, int cc_number, float min_range, float max_range, bool relative_mode) = 0; virtual ControlResponse connect_pc_to_processor(int processor_id, MidiChannel channel, int port) = 0; virtual ControlResponse disconnect_kbd_input(int track_id, MidiChannel channel, int port, bool raw_midi) = 0; virtual ControlResponse disconnect_kbd_output(int track_id, MidiChannel channel, int port) = 0; virtual ControlResponse disconnect_cc(int processor_id, MidiChannel channel, int port, int cc_number) = 0; virtual ControlResponse disconnect_pc(int processor_id, MidiChannel channel, int port) = 0; virtual ControlResponse disconnect_all_cc_from_processor(int processor_id) = 0; virtual ControlResponse disconnect_all_pc_from_processor(int processor_id) = 0; protected: MidiController() = default; }; class AudioRoutingController { public: virtual ~AudioRoutingController() = default; [[nodiscard]] virtual std::vector get_all_input_connections() const = 0; [[nodiscard]] virtual std::vector get_all_output_connections() const = 0; [[nodiscard]] virtual std::pair> get_input_connections_for_track(int track_id) const = 0; [[nodiscard]] virtual std::pair> get_output_connections_for_track(int track_id) const = 0; virtual ControlResponse connect_input_channel_to_track(int track_id, int track_channel, int input_channel) = 0; virtual ControlResponse connect_output_channel_to_track(int track_id, int track_channel, int output_channel) = 0; virtual ControlResponse disconnect_input(int track_id, int track_channel, int input_channel) = 0; virtual ControlResponse disconnect_output(int track_id, int track_channel, int output_channel) = 0; virtual ControlResponse disconnect_all_inputs_from_track(int track_id) = 0; virtual ControlResponse disconnect_all_outputs_from_track(int track_id) = 0; protected: AudioRoutingController() = default; }; class CvGateController { public: virtual ~CvGateController() = default; [[nodiscard]] virtual int get_cv_input_ports() const = 0; [[nodiscard]] virtual int get_cv_output_ports() const = 0; [[nodiscard]] virtual std::vector get_all_cv_input_connections() const = 0; [[nodiscard]] virtual std::vector get_all_cv_output_connections() const = 0; [[nodiscard]] virtual std::vector get_all_gate_input_connections() const = 0; [[nodiscard]] virtual std::vector get_all_gate_output_connections() const = 0; [[nodiscard]] virtual std::pair> get_cv_input_connections_for_processor(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_cv_output_connections_for_processor(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_gate_input_connections_for_processor(int processor_id) const = 0; [[nodiscard]] virtual std::pair> get_gate_output_connections_for_processor(int processor_id) const = 0; virtual ControlResponse connect_cv_input_to_parameter(int processor_id, int parameter_id, int cv_input_id) = 0; virtual ControlResponse connect_cv_output_from_parameter(int processor_id, int parameter_id, int cv_output_id) = 0; virtual ControlResponse connect_gate_input_to_processor(int processor_id, int gate_input_id, int channel, int note_no) = 0; virtual ControlResponse connect_gate_output_from_processor(int processor_id, int gate_output_id, int channel, int note_no) = 0; virtual ControlResponse disconnect_cv_input(int processor_id, int parameter_id, int cv_input_id) = 0; virtual ControlResponse disconnect_cv_output(int processor_id, int parameter_id, int cv_output_id) = 0; virtual ControlResponse disconnect_gate_input(int processor_id, int gate_input_id, int channel, int note_no) = 0; virtual ControlResponse disconnect_gate_output(int processor_id, int gate_output_id, int channel, int note_no) = 0; virtual ControlResponse disconnect_all_cv_inputs_from_processor(int processor_id) = 0; virtual ControlResponse disconnect_all_cv_outputs_from_processor(int processor_id) = 0; virtual ControlResponse disconnect_all_gate_inputs_from_processor(int processor_id) = 0; virtual ControlResponse disconnect_all_gate_outputs_from_processor(int processor_id) = 0; protected: CvGateController() = default; }; class OscController { public: virtual ~OscController() = default; [[nodiscard]] virtual std::string get_send_ip() const = 0; [[nodiscard]] virtual int get_send_port() const = 0; [[nodiscard]] virtual int get_receive_port() const = 0; [[nodiscard]] virtual std::vector get_enabled_parameter_outputs() const = 0; virtual ControlResponse enable_output_for_parameter(int processor_id, int parameter_id) = 0; virtual ControlResponse disable_output_for_parameter(int processor_id, int parameter_id) = 0; virtual ControlResponse enable_all_output() = 0; virtual ControlResponse disable_all_output() = 0; protected: OscController() = default; }; class SessionController { public: virtual ~SessionController() = default; [[nodiscard]] virtual SessionState save_session() const = 0; virtual ControlResponse restore_session(const SessionState& state) = 0; }; class ControlNotification { public: virtual ~ControlNotification() = default; [[nodiscard]] NotificationType type() const {return _type;} [[nodiscard]] Time timestamp() const {return _timestamp;} protected: ControlNotification(NotificationType type, Time timestamp) : _type(type), _timestamp(timestamp) {} private: NotificationType _type; Time _timestamp; }; class ControlListener { public: virtual void notification(const ControlNotification* notification) = 0; }; class SushiControl { public: virtual ~SushiControl() = default; SystemController* system_controller() {return _system_controller;} TransportController* transport_controller() {return _transport_controller;} TimingController* timing_controller() {return _timing_controller;} KeyboardController* keyboard_controller() {return _keyboard_controller;} AudioGraphController* audio_graph_controller() {return _audio_graph_controller;} ProgramController* program_controller() {return _program_controller;} ParameterController* parameter_controller() {return _parameter_controller;} MidiController* midi_controller() {return _midi_controller;} AudioRoutingController* audio_routing_controller() {return _audio_routing_controller;} CvGateController* cv_gate_controller() {return _cv_gate_controller;} OscController* osc_controller() {return _osc_controller;} SessionController* session_controller() {return _session_controller;} virtual ControlStatus subscribe_to_notifications(NotificationType type, ControlListener* listener) = 0; protected: SushiControl(SystemController* system_controller, TransportController* transport_controller, TimingController* timing_controller, KeyboardController* keyboard_controller, AudioGraphController* audio_graph_controller, ProgramController* program_controller, ParameterController* parameter_controller, MidiController* midi_controller, AudioRoutingController* audio_routing_controller, CvGateController* cv_gate_controller, OscController* osc_controller, SessionController* session_controller) : _system_controller(system_controller), _transport_controller(transport_controller), _timing_controller(timing_controller), _keyboard_controller(keyboard_controller), _audio_graph_controller(audio_graph_controller), _program_controller(program_controller), _parameter_controller(parameter_controller), _midi_controller(midi_controller), _audio_routing_controller(audio_routing_controller), _cv_gate_controller(cv_gate_controller), _osc_controller(osc_controller), _session_controller(session_controller){} private: SystemController* _system_controller; TransportController* _transport_controller; TimingController* _timing_controller; KeyboardController* _keyboard_controller; AudioGraphController* _audio_graph_controller; ProgramController* _program_controller; ParameterController* _parameter_controller; MidiController* _midi_controller; AudioRoutingController* _audio_routing_controller; CvGateController* _cv_gate_controller; OscController* _osc_controller; SessionController* _session_controller; }; } // end namespace sushi::control ELK_POP_WARNING #endif //SUSHI_CONTROL_INTERFACE_H ================================================ FILE: include/sushi/control_notifications.h ================================================ #ifndef SUSHI_CONTROL_NOTIFICATIONS_H #define SUSHI_CONTROL_NOTIFICATIONS_H #include #include "control_interface.h" namespace sushi::control { using TransportNotificationValue = std::variant; class TransportNotification : public ControlNotification { public: TransportNotification(TransportAction action, TransportNotificationValue value, Time timestamp) : ControlNotification(NotificationType::TRANSPORT_UPDATE, timestamp), _value(value), _action(action) {} TransportAction action() const {return _action;} TransportNotificationValue value() const {return _value;} private: TransportNotificationValue _value; TransportAction _action; }; class CpuTimingNotification : public ControlNotification { public: CpuTimingNotification(const CpuTimings& timings, Time timestamp) : ControlNotification(NotificationType::CPU_TIMING_UPDATE, timestamp), _cpu_timings(timings) {} CpuTimingNotification(CpuTimings&& timings, Time timestamp) : ControlNotification(NotificationType::CPU_TIMING_UPDATE, timestamp), _cpu_timings(std::move(timings)) {} const CpuTimings& cpu_timings() const {return _cpu_timings;} private: CpuTimings _cpu_timings; }; class TrackNotification : public ControlNotification { public: TrackNotification(TrackAction action, int track_id, Time timestamp) : ControlNotification(NotificationType::TRACK_UPDATE, timestamp), _track_id(track_id), _action(action) {} int track_id() const {return _track_id;} TrackAction action() const {return _action;} private: int _track_id; TrackAction _action; }; class ProcessorNotification : public ControlNotification { public: ProcessorNotification(ProcessorAction action, int processor_id, int parent_track_id, Time timestamp) : ControlNotification(NotificationType::PROCESSOR_UPDATE, timestamp), _processor_id(processor_id), _parent_track_id(parent_track_id), _action(action) {} int processor_id() const {return _processor_id;} int parent_track_id() const {return _parent_track_id;} ProcessorAction action() const {return _action;} private: int _processor_id; int _parent_track_id; ProcessorAction _action; }; class ParameterChangeNotification : public ControlNotification { public: ParameterChangeNotification(int processor_id, int parameter_id, float normalized_value, float domain_value, const std::string& formatted_value, Time timestamp) : ControlNotification(NotificationType::PARAMETER_CHANGE, timestamp), _processor_id(processor_id), _parameter_id(parameter_id), _normalized_value(normalized_value), _domain_value(domain_value), _formatted_value(formatted_value) {} int processor_id() const {return _processor_id;} int parameter_id() const {return _parameter_id;} float value() const {return _normalized_value;} float domain_value() const {return _domain_value;} const std::string& formatted_value() const {return _formatted_value;}; private: int _processor_id; int _parameter_id; float _normalized_value; float _domain_value; std::string _formatted_value; }; class PropertyChangeNotification : public ControlNotification { public: PropertyChangeNotification(int processor_id, int property_id, const std::string& value, Time timestamp) : ControlNotification(NotificationType::PROPERTY_CHANGE, timestamp), _processor_id(processor_id), _property_id(property_id), _value(value) {} PropertyChangeNotification(int processor_id, int property_id, std::string&& value, Time timestamp) : ControlNotification(NotificationType::PROPERTY_CHANGE, timestamp), _processor_id(processor_id), _property_id(property_id), _value(value) {} int processor_id() const {return _processor_id;} int parameter_id() const {return _property_id;} const std::string& value() const {return _value;} private: int _processor_id; int _property_id; std::string _value; }; class CommandCompletionNotification : public ControlNotification { public: CommandCompletionNotification(ControlStatus status, int id, Time timestamp) : ControlNotification(NotificationType::ASYNC_COMMAND_COMPLETION, timestamp), _status(status), _id(id) {} ControlStatus status() const {return _status;} int id() const {return _id;} private: ControlStatus _status; int _id; }; } // end namespace sushi::control #endif //SUSHI_CONTROL_NOTIFICATIONS_H ================================================ FILE: include/sushi/coreaudio_devices_dump.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Utility functions for dumping CoreAudio devices info * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #include "rapidjson/document.h" namespace sushi { /** * @brief Retrieve CoreAudio's registered devices information. * Can be queried before instantiating an actual CoreAudioFrontend * * @return Device information list in JSON format */ rapidjson::Document generate_coreaudio_devices_info_document(); } // end namespace sushi ================================================ FILE: include/sushi/elk_sentry_log_sink.h ================================================ /** * @brief An spdlog sink which wraps the Sentry logging functionality * @Copyright 2017-2024 Elk Audio AB, Stockholm */ #ifndef ELK_SENTRY_LOG_SINK_H #define ELK_SENTRY_LOG_SINK_H #include #include #include "spdlog/details/null_mutex.h" #include "spdlog/sinks/base_sink.h" #include "sentry.h" namespace elk { template class SentrySink : public spdlog::sinks::base_sink { public: SentrySink() : spdlog::sinks::base_sink() { } ~SentrySink() { } protected: void sink_it_(const spdlog::details::log_msg& msg) override { const std::string payload(msg.payload.begin(), msg.payload.end()); const std::string logger_name(msg.logger_name.begin(), msg.logger_name.end()); switch (msg.level) { case spdlog::level::info: _add_breadcrumb(payload, logger_name, "info"); break; case spdlog::level::debug: _add_breadcrumb(payload, logger_name, "debug"); break; case spdlog::level::warn: _add_breadcrumb(payload, logger_name, "warning"); break; case spdlog::level::err: sentry_capture_event(sentry_value_new_message_event( SENTRY_LEVEL_ERROR, // level logger_name.c_str(), // logger name payload.c_str() // message )); break; default: break; } } void flush_() override { sentry_flush(1000); } private: void _add_breadcrumb(const std::string& message, const std::string& category, const std::string& level) { sentry_value_t crumb = sentry_value_new_breadcrumb("log", message.c_str()); sentry_value_set_by_key(crumb, "category", sentry_value_new_string(category.c_str())); sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level.c_str())); sentry_add_breadcrumb(crumb); } }; using sentry_sink_mt = SentrySink; using sentry_sink_st = SentrySink; } #endif // SUSHI_SENTRY_LOG_SINK_H ================================================ FILE: include/sushi/factory_interface.h ================================================ /* * Copyright 2017-2022 Modern Ancient Instruments Networked AB, dba Elk * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ #ifndef FACTORY_INTERFACE_H #define FACTORY_INTERFACE_H #include "sushi.h" namespace sushi { class FactoryInterface { public: FactoryInterface() = default; virtual ~FactoryInterface() = default; [[nodiscard]] virtual std::pair, Status> new_instance(SushiOptions& options) = 0; }; } // end namespace sushi #endif // FACTORY_INTERFACE_H ================================================ FILE: include/sushi/offline_factory.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Public Sushi factory for offline use. * @copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef OFFLINE_FACTORY_H #define OFFLINE_FACTORY_H #include "factory_interface.h" #include "sushi.h" namespace sushi { namespace internal { class OfflineFactoryImplementation; } class OfflineFactory : public FactoryInterface { public: OfflineFactory(); ~OfflineFactory() override; [[nodiscard]] std::pair, Status> new_instance(SushiOptions& options) override; private: std::unique_ptr _implementation; }; } // end namespace sushi #endif // STANDALONE_FACTORY_H ================================================ FILE: include/sushi/options.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Option parsing * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_OPTIONS_H #define SUSHI_OPTIONS_H #include #include "elk-warning-suppressor/warning_suppressor.hpp" ELK_PUSH_WARNING ELK_DISABLE_ZERO_AS_NULL_POINTER_CONSTANT ELK_DISABLE_SIGN_CONVERSION ELK_DISABLE_CONDITIONAL_UNINITIALIZED #include "optionparser.h" ELK_POP_WARNING #define _SUSHI_STRINGIZE(X) #X #define SUSHI_STRINGIZE(X) _SUSHI_STRINGIZE(X) //////////////////////////////////////////////////////////////////////////////// // Options Defaults //////////////////////////////////////////////////////////////////////////////// #define ELKLOG_LOG_LEVEL_DEFAULT "info" #define ELKLOG_LOG_FILE_DEFAULT "/tmp/sushi.log" #define SUSHI_JSON_FILENAME_DEFAULT "config.json" #define SUSHI_JSON_STRING_DEFAULT "{}" #define SUSHI_SAMPLE_RATE_DEFAULT 48000 #define SUSHI_JACK_CLIENT_NAME_DEFAULT "sushi" #define SUSHI_OSC_SERVER_PORT_DEFAULT 24024 #define SUSHI_OSC_SEND_PORT_DEFAULT 24023 #define SUSHI_OSC_SEND_IP_DEFAULT "127.0.0.1" #if defined(_MSC_VER) #define SUSHI_GRPC_LISTENING_PORT_DEFAULT "[::]:510" #else #define SUSHI_GRPC_LISTENING_PORT_DEFAULT "[::]:51051" #endif #define SUSHI_PORTAUDIO_INPUT_LATENCY_DEFAULT 0.0f #define SUSHI_PORTAUDIO_OUTPUT_LATENCY_DEFAULT 0.0f #define SUSHI_SENTRY_CRASH_HANDLER_PATH_DEFAULT "./crashpad_handler" #ifdef SUSHI_BUILD_WITH_SENTRY #define SUSHI_SENTRY_DSN_DEFAULT SUSHI_SENTRY_DSN #else #define SUSHI_SENTRY_DSN_DEFAULT "" #endif namespace sushi { //////////////////////////////////////////////////////////////////////////////// // Helpers for optionparse //////////////////////////////////////////////////////////////////////////////// struct SushiArg : public optionparser::Arg { static void print_error(const char* msg1, const optionparser::Option& opt, const char* msg2) { fprintf(stderr, "%s", msg1); fwrite(opt.name, static_cast(opt.namelen), 1, stderr); fprintf(stderr, "%s", msg2); } static optionparser::ArgStatus Unknown(const optionparser::Option& option, bool msg) { if (msg) { print_error("Unknown option '", option, "'\n"); } return optionparser::ARG_ILLEGAL; } static optionparser::ArgStatus NonEmpty(const optionparser::Option& option, bool msg) { if (option.arg && option.arg[0]) { return optionparser::ARG_OK; } if (msg) { print_error("Option '", option, "' requires a non-empty argument\n"); } return optionparser::ARG_ILLEGAL; } static optionparser::ArgStatus Numeric(const optionparser::Option& option, bool msg) { char* endptr = nullptr; if (option.arg != nullptr && strtol(option.arg, &endptr, 10)) {} if (endptr != option.arg && *endptr == 0) { return optionparser::ARG_OK; } if (msg) { print_error("Option '", option, "' requires a numeric argument\n"); } return optionparser::ARG_ILLEGAL; } }; //////////////////////////////////////////////////////////////////////////////// // Command Line options descriptors //////////////////////////////////////////////////////////////////////////////// // List here the different command line options enum OptionIndex { OPT_IDX_UNKNOWN, OPT_IDX_HELP, OPT_IDX_VERSION, OPT_IDX_LOG_LEVEL, OPT_IDX_LOG_FILE, OPT_IDX_LOG_FLUSH_INTERVAL, OPT_IDX_DUMP_PARAMETERS, OPT_IDX_CONFIG_FILE, OPT_IDX_USE_OFFLINE, OPT_IDX_INPUT_FILE, OPT_IDX_OUTPUT_FILE, OPT_IDX_USE_DUMMY, OPT_IDX_USE_PORTAUDIO, OPT_IDX_USE_APPLE_COREAUDIO, OPT_IDX_AUDIO_INPUT_DEVICE, OPT_IDX_AUDIO_INPUT_DEVICE_UID, OPT_IDX_AUDIO_OUTPUT_DEVICE, OPT_IDX_AUDIO_OUTPUT_DEVICE_UID, OPT_IDX_PA_SUGGESTED_INPUT_LATENCY, OPT_IDX_PA_SUGGESTED_OUTPUT_LATENCY, OPT_IDX_DUMP_DEVICES, OPT_IDX_USE_JACK, OPT_IDX_CONNECT_PORTS, OPT_IDX_JACK_CLIENT, OPT_IDX_JACK_SERVER, OPT_IDX_USE_XENOMAI_RASPA, OPT_IDX_XENOMAI_DEBUG_MODE_SW, OPT_IDX_MULTICORE_PROCESSING, OPT_IDX_TIMINGS_STATISTICS, OPT_IDX_DETAILED_TIMINGS, OPT_IDX_OSC_RECEIVE_PORT, OPT_IDX_OSC_SEND_PORT, OPT_IDX_OSC_SEND_IP, OPT_IDX_GRPC_LISTEN_ADDRESS, OPT_IDX_NO_OSC, OPT_IDX_NO_GRPC, OPT_IDX_BASE_PLUGIN_PATH, OPT_IDX_SENTRY_CRASH_HANDLER, OPT_IDX_SENTRY_DSN }; // Option types (UNUSED is generally used for options that take a value as argument) enum OptionType { OPT_TYPE_UNUSED = 0, OPT_TYPE_DISABLED = 1, OPT_TYPE_ENABLED = 2 }; // Option descriptors, one for each entry of the OptionIndex enum const optionparser::Descriptor usage[] = { { OPT_IDX_UNKNOWN, // index OPT_TYPE_UNUSED, // type "", // shortopt "", // longopt SushiArg::Unknown, // check_arg "\nUSAGE: sushi -r|-j|-o|-d [options] \n\nOptions:" // help }, { OPT_IDX_HELP, OPT_TYPE_UNUSED, "h?", "help", SushiArg::None, "\t\t-h --help \tPrint usage and exit." }, { OPT_IDX_VERSION, OPT_TYPE_UNUSED, "v", "version", SushiArg::None, "\t\t-v --version \tPrint version information and exit." }, { OPT_IDX_LOG_LEVEL, OPT_TYPE_UNUSED, "l", "log-level", SushiArg::NonEmpty, "\t\t-l , --log-level= \tSpecify minimum logging level, from ('debug', 'info', 'warning', 'error') [default=" ELKLOG_LOG_LEVEL_DEFAULT "]." }, { OPT_IDX_LOG_FILE, OPT_TYPE_UNUSED, "L", "log-file", SushiArg::NonEmpty, "\t\t-L , --log-file= \tSpecify logging file destination [default=" ELKLOG_LOG_FILE_DEFAULT "]." }, { OPT_IDX_LOG_FLUSH_INTERVAL, OPT_TYPE_UNUSED, "", "log-flush-interval", SushiArg::NonEmpty, "\t\t--log-flush-interval= \tEnable flushing the log periodically and specify the interval." }, { OPT_IDX_DUMP_PARAMETERS, OPT_TYPE_DISABLED, "", "dump-plugins", SushiArg::Optional, "\t\t--dump-plugins \tDump plugin and parameter data to stdout in JSON format." }, { OPT_IDX_CONFIG_FILE, OPT_TYPE_UNUSED, "c", "config-file", SushiArg::NonEmpty, "\t\t-c , --config-file= \tSpecify configuration JSON file [default=" SUSHI_JSON_FILENAME_DEFAULT "]." }, { OPT_IDX_USE_OFFLINE, OPT_TYPE_DISABLED, "o", "offline", SushiArg::Optional, "\t\t-o --offline \tUse offline file audio frontend." }, { OPT_IDX_INPUT_FILE, OPT_TYPE_UNUSED, "i", "input", SushiArg::NonEmpty, "\t\t-i , --input= \tSpecify input file, required for --offline option." }, { OPT_IDX_OUTPUT_FILE, OPT_TYPE_UNUSED, "O", "output", SushiArg::NonEmpty, "\t\t-O , --output= \tSpecify output file [default= (input_file).proc.wav]." }, { OPT_IDX_USE_DUMMY, OPT_TYPE_DISABLED, "d", "dummy", SushiArg::Optional, "\t\t-d --dummy \tUse dummy audio frontend. Useful for debugging." }, { OPT_IDX_USE_PORTAUDIO, OPT_TYPE_DISABLED, "a", "portaudio", SushiArg::Optional, "\t\t-a --portaudio \tUse PortAudio realtime audio frontend." }, { OPT_IDX_USE_APPLE_COREAUDIO, OPT_TYPE_DISABLED, "", "coreaudio", SushiArg::Optional, "\t\t--coreaudio \tUse Apple CoreAudio realtime audio frontend." }, { OPT_IDX_AUDIO_INPUT_DEVICE, OPT_TYPE_UNUSED, "", "audio-input-device", SushiArg::Optional, "\t\t--audio-input-device= \tIndex of the device to use for audio input with portaudio frontend [default=system default]" }, { OPT_IDX_AUDIO_OUTPUT_DEVICE, OPT_TYPE_UNUSED, "", "audio-output-device", SushiArg::Optional, "\t\t--audio-output-device= \tIndex of the device to use for audio output with portaudio frontend [default=system default]" }, { OPT_IDX_AUDIO_INPUT_DEVICE_UID, OPT_TYPE_UNUSED, "", "audio-input-device-uid", SushiArg::Optional, "\t\t--audio-input-device-uid= \tUID of the device to use for audio input with Apple CoreAudio frontend [default=system default]" }, { OPT_IDX_AUDIO_OUTPUT_DEVICE_UID, OPT_TYPE_UNUSED, "", "audio-output-device-uid", SushiArg::Optional, "\t\t--audio-output-device-uid= \tUID of the device to use for audio output with Apple CoreAudio frontend [default=system default]" }, { OPT_IDX_PA_SUGGESTED_INPUT_LATENCY, OPT_TYPE_UNUSED, "", "pa-suggested-input-latency", SushiArg::Optional, "\t\t--pa-suggested-input-latency= \tInput latency in seconds to suggest to portaudio. Will be rounded up to closest available latency depending on audio API [default=0.0]" }, { OPT_IDX_PA_SUGGESTED_OUTPUT_LATENCY, OPT_TYPE_UNUSED, "", "pa-suggested-output-latency", SushiArg::Optional, "\t\t--pa-suggested-output-latency= \tOutput latency in seconds to suggest to portaudio. Will be rounded up to closest available latency depending on audio API [default=0.0]" }, { OPT_IDX_DUMP_DEVICES, OPT_TYPE_DISABLED, "", "dump-audio-devices", SushiArg::Optional, "\t\t--dump-audio-devices \tDump available audio devices to stdout in JSON format. Requires a frontend to be specified." }, { OPT_IDX_USE_JACK, OPT_TYPE_DISABLED, "j", "jack", SushiArg::Optional, "\t\t-j --jack \tUse Jack realtime audio frontend." }, { OPT_IDX_CONNECT_PORTS, OPT_TYPE_DISABLED, "", "connect-ports", SushiArg::Optional, "\t\t--connect-ports \tTry to automatically connect Jack ports at startup." }, { OPT_IDX_JACK_CLIENT, OPT_TYPE_UNUSED, "", "client-name", SushiArg::NonEmpty, "\t\t--client-name= \tSpecify name of Jack client [default=sushi]." }, { OPT_IDX_JACK_SERVER, OPT_TYPE_UNUSED, "", "server-name", SushiArg::NonEmpty, "\t\t--server-name= \tSpecify name of Jack server to connect to [determined by jack if empty]." }, { OPT_IDX_USE_XENOMAI_RASPA, OPT_TYPE_DISABLED, "r", "raspa", SushiArg::Optional, "\t\t-r --raspa \tUse Xenomai real-time frontend with RASPA driver." }, { OPT_IDX_XENOMAI_DEBUG_MODE_SW, OPT_TYPE_DISABLED, "", "debug-mode-sw", SushiArg::Optional, "\t\t--debug-mode-sw \tBreak to debugger if a mode switch is detected (Xenomai only)." }, { OPT_IDX_MULTICORE_PROCESSING, OPT_TYPE_UNUSED, "m", "multicore-processing", SushiArg::Numeric, "\t\t-m , --multicore-processing= \tProcess audio multithreaded with n cores [default n=1 (off)]." }, { OPT_IDX_TIMINGS_STATISTICS, OPT_TYPE_DISABLED, "", "timing-statistics", SushiArg::Optional, "\t\t--timing-statistics \tEnable performance timings on all audio processors." }, { OPT_IDX_DETAILED_TIMINGS, OPT_TYPE_UNUSED, "", "detailed-timings", SushiArg::NonEmpty, "\t\t--detailed-timings \tRecord timings for all audio callbacks for the given processor name. Use only for debug purposes as it will record significant amounts of data. Multiple processor names may be passed separated by semicolon" }, { OPT_IDX_OSC_RECEIVE_PORT, OPT_TYPE_UNUSED, "p", "osc-rcv-port", SushiArg::NonEmpty, "\t\t-p --osc-rcv-port= \tPort to listen for OSC messages on [default port=" SUSHI_STRINGIZE( SUSHI_OSC_SERVER_PORT_DEFAULT) "]." }, { OPT_IDX_OSC_SEND_PORT, OPT_TYPE_UNUSED, "", "osc-send-port", SushiArg::NonEmpty, "\t\t--osc-send-port= \tPort to output OSC messages to [default port=" SUSHI_STRINGIZE( SUSHI_OSC_SEND_PORT_DEFAULT) "]." }, { OPT_IDX_OSC_SEND_IP, OPT_TYPE_UNUSED, "", "osc-send-ip", SushiArg::NonEmpty, "\t\t--osc-send-ip= \tIP to output OSC messages to [default port=" SUSHI_STRINGIZE( SUSHI_OSC_SEND_IP_DEFAULT) "]." }, { OPT_IDX_GRPC_LISTEN_ADDRESS, OPT_TYPE_UNUSED, "", "grpc-address", SushiArg::NonEmpty, "\t\t--grpc-address= \tgRPC listening address in the format: address:port. By default accepts incoming connections from all ip:s [default port=" SUSHI_GRPC_LISTENING_PORT_DEFAULT "]." }, { OPT_IDX_NO_OSC, OPT_TYPE_DISABLED, "", "no-osc", SushiArg::Optional, "\t\t--no-osc \tDisable Open Sound Control completely" }, { OPT_IDX_NO_GRPC, OPT_TYPE_DISABLED, "", "no-grpc", SushiArg::Optional, "\t\t--no-grpc \tDisable gRPC Control completely" }, { OPT_IDX_BASE_PLUGIN_PATH, OPT_TYPE_UNUSED, "", "base-plugin-path", SushiArg::NonEmpty, "\t\t--base-plugin-path= \tSpecify a directory to be the base of plugin paths used in JSON / gRPC." }, { OPT_IDX_SENTRY_CRASH_HANDLER, OPT_TYPE_UNUSED, "", "sentry-crash-handler", SushiArg::NonEmpty, "\t\t--sentry-crash-handler= \tSet the path to the crash handler to use for sentry reports [default path=" SUSHI_STRINGIZE(SUSHI_SENTRY_CRASH_HANDLER_PATH_DEFAULT) "]." }, { OPT_IDX_SENTRY_DSN, OPT_TYPE_UNUSED, "", "sentry-dsn", SushiArg::NonEmpty, "\t\t--sentry-dsn= \tSet the DSN that sentry should upload crash logs to [default address=" SUSHI_STRINGIZE(SUSHI_SENTRY_DSN_DEFAULT) "]." }, // Don't touch this one (sets default values for optionparser library) { 0, 0, nullptr, nullptr, nullptr, nullptr} }; } // end namespace sushi #endif // SUSHI_OPTIONS_H ================================================ FILE: include/sushi/parameter_dump.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Utility functions for writing parameter names to a file. * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_PARAMETER_DUMP_H #define SUSHI_PARAMETER_DUMP_H #include "rapidjson/document.h" namespace sushi { namespace control { class SushiControl; } rapidjson::Document generate_processor_parameter_document(sushi::control::SushiControl* engine_controller); } // end namespace sushi #endif //SUSHI_PARAMETER_DUMP_H ================================================ FILE: include/sushi/portaudio_devices_dump.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Utility functions for dumping Portaudio devices info * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #include "rapidjson/document.h" namespace sushi { /** * @brief Retrieve Portaudio's registered devices information. * Can be queried before instantiating an actual PortaudioFrontend * * @return Device information list in JSON format */ rapidjson::Document generate_portaudio_devices_info_document(); } // end namespace sushi ================================================ FILE: include/sushi/reactive_factory.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Public Sushi factory for reactive use. * @copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef REACTIVE_FACTORY_H #define REACTIVE_FACTORY_H #include "factory_interface.h" #include "sushi.h" namespace sushi { namespace internal { class ReactiveFactoryImplementation; } class RtController; class ReactiveFactory : public FactoryInterface { public: ReactiveFactory(); ~ReactiveFactory() override; [[nodiscard]] std::pair, Status> new_instance(SushiOptions& options) override; /** * @brief Returns an instance of a RealTimeController, if new_instance(...) completed successfully. * If not, it returns an empty unique_ptr. * @return A unique_ptr with a RtController sub-class, or not, depending on InitStatus. */ [[nodiscard]] std::unique_ptr rt_controller(); private: std::unique_ptr _implementation; }; } // end namespace sushi #endif // REACTIVE_FACTORY_H ================================================ FILE: include/sushi/rt_controller.h ================================================ /* * Copyright 2017-2022 Modern Ancient Instruments Networked AB, dba Elk * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ #ifndef REAL_TIME_CONTROLLER_H #define REAL_TIME_CONTROLLER_H #include #include "control_interface.h" #include "sushi_time.h" #include "types.h" #include "sample_buffer.h" namespace sushi { enum class TransportPositionSource { EXTERNAL, CALCULATED }; using ReactiveMidiCallback = std::function; /** * @brief The API for the methods which can safely be called from a real-time context to interact with Sushi as a library. */ class RtController { public: RtController() = default; virtual ~RtController() = default; /// For Transport: ///////////////////////////////////////////////////////////// /** * @brief Set the tempo of the Sushi transport. * (can be called from a real-time context). * @param tempo */ virtual void set_tempo(float tempo) = 0; /** * @brief Set the time signature of the Sushi transport. * (can be called from a real-time context). * @param time_signature */ virtual void set_time_signature(control::TimeSignature time_signature) = 0; /** * @brief Set the PlayingMode of the Sushi transport. * (can be called from a real-time context). * @param mode */ virtual void set_playing_mode(control::PlayingMode mode) = 0; /** * @brief Set the beat time of the Sushi transport. * (can be called from a real-time context). * @param beat_time * @return true if the beat time was set, false if PositionSource is not set to EXTERNAL */ virtual bool set_current_beats(double beat_time) = 0; /** * @brief Set the bar beat count of the Sushi transport. * (can be called from a real-time context). * @param bar_beat_count * @return true if the bar beat time was set, false if PositionSource is not set to EXTERNAL */ virtual bool set_current_bar_beats(double bar_beat_count) = 0; /** * @brief Sets which source to use for the beat count position: the internally calculated one, or the one set * using the set_current_beats method below. * @param TransportPositionSource Enum, EXTERNAL / CALCULATED */ virtual void set_position_source(TransportPositionSource ps) = 0; /// For Audio: ///////////////////////////////////////////////////////////// /** * @brief Method to invoke from the host's audio callback. * @param in_buffer Input sample buffer * @param out_buffer Output sample buffer * @param timestamp timestamp for call */ virtual void process_audio(ChunkSampleBuffer& in_buffer, ChunkSampleBuffer& out_buffer, Time timestamp) = 0; /** * @brief Call before the first call to process_audio() when resuming from an interrupt or xrun to * notify sushi that audio processing was interrupted and that there may be gaps in the audio * @param duration The length of the interruption */ virtual void notify_interrupted_audio(Time duration) = 0; /// For MIDI: ///////////////////////////////////////////////////////////// /** * @brief Call to pass MIDI input to Sushi * @param input Currently assumed to always be 0 since the frontend only supports a single input device. * @param data MidiDataByte * @param timestamp Sushi Time timestamp for message */ virtual void receive_midi(int input, MidiDataByte data, Time timestamp) = 0; /** * @brief Assign a callback which is invoked when a MIDI message is generated from inside Sushi. * (Not safe to call from a real-time context, and should only really be called once). * @param callback */ virtual void set_midi_callback(ReactiveMidiCallback&& callback) = 0; /** * @brief If the host doesn't provide a timestamp, this method can be used to calculate it, * based on the sample count from session start. * @return The currently calculated Timestamp. */ virtual sushi::Time calculate_timestamp_from_start(float sample_rate) const = 0; /** * @brief Call this at the end of each ProcessBlock, to update the sample count and timestamp used for * time and sample offset calculations. * @param sample_count * @param timestamp */ virtual void increment_samples_since_start(int64_t sample_count, Time timestamp) = 0; }; } // end namespace sushi #endif //REAL_TIME_CONTROLLER_H ================================================ FILE: include/sushi/sample_buffer.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief General purpose multichannel audio buffer class * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_SAMPLEBUFFER_H #define SUSHI_SAMPLEBUFFER_H #include #include #include #include "constants.h" namespace sushi { constexpr int LEFT_CHANNEL_INDEX = 0; constexpr int RIGHT_CHANNEL_INDEX = 1; template class SampleBuffer; template void swap(SampleBuffer& lhs, SampleBuffer& rhs) { std::swap(lhs._channel_count, rhs._channel_count); std::swap(lhs._own_buffer, rhs._own_buffer); std::swap(lhs._buffer, rhs._buffer); } template class SampleBuffer { public: /** * @brief Construct a zeroed buffer with specified number of channels */ explicit SampleBuffer(int channel_count) : _channel_count(channel_count), _own_buffer(true), _buffer(new float[static_cast(size * channel_count)]) { clear(); } /** * @brief Construct an empty buffer object with 0 channels. */ SampleBuffer() noexcept : _channel_count(0), _own_buffer(true), _buffer(nullptr) {} /** * @brief Copy constructor. */ SampleBuffer(const SampleBuffer &o) : _channel_count(o._channel_count), _own_buffer(o._own_buffer) { if (o._own_buffer) { _buffer = new float[size * o._channel_count]; std::copy(o._buffer, o._buffer + (size * o._channel_count), _buffer); } else { _buffer = o._buffer; } } /** * @brief Move constructor. */ SampleBuffer(SampleBuffer &&o) noexcept : _channel_count(o._channel_count), _own_buffer(o._own_buffer), _buffer(o._buffer) { o._buffer = nullptr; } /** * @brief Destroy the buffer. */ ~SampleBuffer() { if (_own_buffer) { delete[] _buffer; } } /** * @brief Assign to this buffer. */ SampleBuffer &operator=(const SampleBuffer &o) { if (this != &o) // Avoid self-assignment { if (_own_buffer && o._own_buffer) { if (_channel_count != o._channel_count) { delete[] _buffer; _buffer = (o._channel_count > 0)? (new float[size * o._channel_count]) : nullptr; _channel_count = o._channel_count; } } else { /* Assigning to or from a non owning buffer is only allowed if their * channel count matches. In that case the underlying sample data is * copied. * If their sample counts differs this might trigger a (re)allocation * of the internal data buffer, This would need to be resolved by * either forcing the SampleBuffer owning the data to change its * channel count or turn the non-owning SampleBuffer into a normal * SampleBuffer that owns its data buffer, and hence losing the * connection to the SampleBuffer that originally owned the data. * Both of which will have unexpected, and most likely unwanted, side * effects. */ assert(_channel_count == o._channel_count); } std::copy(o._buffer, o._buffer + (size * o._channel_count), _buffer); } return *this; } /** * @brief Assign to this buffer using move semantics. */ SampleBuffer &operator=(SampleBuffer &&o) noexcept { if (this != &o) // Avoid self-assignment { if (_own_buffer) { delete[] _buffer; } _channel_count = o._channel_count; _own_buffer = o._own_buffer; _buffer = o._buffer; o._buffer = nullptr; } return *this; } /** * @brief Create a SampleBuffer from another SampleBuffer, without copying or * taking ownership of the data. Optionally only a subset of * the source buffers channels can be wrapped. * @param source The SampleBuffer whose data is wrapped. * @param start_channel The first channel to wrap. Defaults to 0. * @param number_of_channels Must not exceed the channel count of the source buffer * minus start_channel. * @return The created, non-owning SampleBuffer. */ static SampleBuffer create_non_owning_buffer(SampleBuffer& source, int start_channel, int number_of_channels) { assert(number_of_channels + start_channel <= source._channel_count); SampleBuffer buffer; buffer._own_buffer = false; buffer._channel_count = number_of_channels; buffer._buffer = source._buffer + size * start_channel; return buffer; } /** * @brief Defaulted version of the above function. */ static SampleBuffer create_non_owning_buffer(SampleBuffer& source) { return create_non_owning_buffer(source, 0, source.channel_count()); } /** * @brief Create a SampleBuffer by wrapping a raw data pointer. * * @param data raw pointer to data stored in the same format of SampleBuffer storage * @param start_channel Index of first channel to wrap. * @param start_channel The first channel to wrap. Defaults to 0. * @param number_of_channels Must not exceed the channel count of the source buffer * minus start_channel. * @return The created, non-owning SampleBuffer. */ static SampleBuffer create_from_raw_pointer(float* data, int start_channel, int number_of_channels) { SampleBuffer buffer; buffer._own_buffer = false; buffer._channel_count = number_of_channels; buffer._buffer = data + size * start_channel; return buffer; } /** * @brief Zero the entire buffer */ void clear() { std::fill(_buffer, _buffer + (size * _channel_count), 0.0f); } /** * @brief Returns a writeable pointer to a specific channel in the buffer. No bounds checking. */ float* channel(int channel) { return _buffer + channel * size; } /** * @brief Returns a read-only pointer to a specific channel in the buffer. No bounds checking. */ const float* channel(int channel) const { return _buffer + channel * size; } /** * @brief Gets the number of channels in the buffer. */ int channel_count() const { return _channel_count; } /** * @brief Copy interleaved audio data from interleaved_buf to this buffer. */ void from_interleaved(const float* interleaved_buf) { switch (_channel_count) { case 2: // Most common case, others are mostly included for future compatibility { float* l_in = _buffer; float* r_in = _buffer + size; for (int n = 0; n < size; ++n) { *l_in++ = *interleaved_buf++; *r_in++ = *interleaved_buf++; } break; } case 1: { std::copy(interleaved_buf, interleaved_buf + size, _buffer); break; } default: { for (int n = 0; n < size; ++n) { for (int c = 0; c < _channel_count; ++c) { _buffer[n + c * _channel_count] = *interleaved_buf++; } } } } } /** * @brief Copy buffer data in interleaved format to interleaved_buf */ void to_interleaved(float* interleaved_buf) const { switch (_channel_count) { case 2: // Most common case, others are mostly included for future compatibility { float* l_out = _buffer; float* r_out = _buffer + size; for (int n = 0; n < size; ++n) { *interleaved_buf++ = *l_out++; *interleaved_buf++ = *r_out++; } break; } case 1: { std::copy(_buffer, _buffer + size, interleaved_buf); break; } default: { for (int n = 0; n < size; ++n) { for (int c = 0; c < _channel_count; ++c) { *interleaved_buf++ = _buffer[n + c * size]; } } } } } /** * @brief Apply a fixed gain to the entire buffer. */ void apply_gain(float gain) { for (int i = 0; i < size * _channel_count; ++i) { _buffer[i] *= gain; } } /** * @brief Apply a fixed gain to a given channel. */ void apply_gain(float gain, int channel) { float* data = _buffer + size * channel; for (int i = 0; i < size; ++i) { data[i] *= gain; } } /** * @brief Replace the contents of the buffer with that of another buffer * @param source SampleBuffer with either 1 channel or the same number of * channels as the destination buffer */ void replace(const SampleBuffer &source) { assert(source.channel_count() == 1 || source.channel_count() == this->channel_count()); if (source.channel_count() == 1) // mono input, copy to all dest channels { for (int channel = 0; channel < _channel_count; ++channel) { std::copy(source._buffer, source._buffer + size, _buffer + channel * size); } } else { std::copy(source._buffer, source._buffer + _channel_count * size, _buffer); } } /** * @brief Copy data channel by channel into this buffer from source buffer. No bounds checking. */ void replace(int dest_channel, int source_channel, const SampleBuffer &source) { assert(source_channel < source.channel_count() && dest_channel < this->channel_count()); std::copy(source.channel(source_channel), source.channel(source_channel) + size, _buffer + (dest_channel * size)); } /** * @brief Sums the content of source into this buffer. * @param source SampleBuffer with either 1 channel or the same number of * channels as the destination buffer */ void add(const SampleBuffer &source) { assert(source.channel_count() == 1 || source.channel_count() == this->channel_count()); if (source.channel_count() == 1) // mono input, add to all dest channels { for (int channel = 0; channel < _channel_count; ++channel) { float* dest = _buffer + size * channel; for (int i = 0; i < size; ++i) { dest[i] += source._buffer[i]; } } } else if (source.channel_count() == _channel_count) { for (int i = 0; i < size * _channel_count; ++i) { _buffer[i] += source._buffer[i]; } } } /** * @brief Sums one channel of source buffer into one channel of the buffer. */ void add(int dest_channel, int source_channel, const SampleBuffer& source) { float* source_data = source._buffer + size * source_channel; float* dest_data = _buffer + size * dest_channel; for (int i = 0; i < size; ++i) { dest_data[i] += source_data[i]; } } /** * @brief Sums the content of SampleBuffer source into this buffer after applying a gain. * * source has to be either a 1 channel buffer or have the same number of channels * as the destination buffer. */ void add_with_gain(const SampleBuffer &source, float gain) { assert(source.channel_count() == 1 || source.channel_count() == this->channel_count()); if (source.channel_count() == 1) { for (int channel = 0; channel < _channel_count; ++channel) { float* dest = _buffer + size * channel; for (int i = 0; i < size; ++i) { dest[i] += source._buffer[i] * gain; } } } else if (source.channel_count() == _channel_count) { for (int i = 0; i < size * _channel_count; ++i) { _buffer[i] += source._buffer[i] * gain; } } } /** * @brief Sums one channel of source buffer into one channel of the buffer after applying gain. */ void add_with_gain(int dest_channel, int source_channel, const SampleBuffer& source, float gain) { float* source_data = source._buffer + size * source_channel; float* dest_data = _buffer + size * dest_channel; for (int i = 0; i < size; ++i) { dest_data[i] += source_data[i] * gain; } } /** * @brief Sums the content of SampleBuffer source into this buffer after applying a * linear gain ramp. * * @param source The buffer to copy from. Has to be either a 1 channel buffer or have * the same number of channels as this buffer * @param start The value to start the ramp from * @param end The value to end the ramp */ void add_with_ramp(const SampleBuffer &source, float start, float end) { assert(source.channel_count() == 1 || source.channel_count() == _channel_count); float inc = (end - start) / (size - 1); if (source.channel_count() == 1) { for (int channel = 0; channel < _channel_count; ++channel) { float* dest = _buffer + size * channel; for (int i = 0; i < size; ++i) { dest[i] += source._buffer[i] * (start + i * inc); } } } else if (source.channel_count() == _channel_count) { for (int channel = 0; channel < _channel_count; ++channel) { float* source_data = _buffer + size * channel; float* dest_data = _buffer + size * channel; for (int i = 0; i < size; ++i) { dest_data[i] += source_data[i] * (start + i * inc); } } } } /** * @brief Sums one channel of source buffer into one channel of the buffer after applying * a linear gain ramp. */ void add_with_ramp(int dest_channel, int source_channel, const SampleBuffer& source, float start, float end) { float inc = (end - start) / (size - 1); float* source_data = source._buffer + size * source_channel; float* dest_data = _buffer + size * dest_channel; for (int i = 0; i < size; ++i) { dest_data[i] += source_data[i] * (start + i * inc); } } /** * @brief Ramp the volume of all channels linearly from start to end * @param start The value to start the ramp from * @param end The value to end the ramp */ void ramp(float start, float end) { float inc = (end - start) / (size - 1); for (int channel = 0; channel < _channel_count; ++channel) { float* data = _buffer + size * channel; for (int i = 0; i < size; ++i) { data[i] *= start + static_cast(i) * inc; } } } /** * @brief Convenience wrapper for ramping up from 0 to unity */ void ramp_up() { this->ramp(0.0f, 1.0f); } /** * @brief Convenience wrapper for ramping from unity down to 0 */ void ramp_down() { this->ramp(1.0f, 0.0f); } /** * @brief Count the number of samples outside of [-1.0, 1.0] in one channel * @param channel The channel to analyse, must not exceed the buffer's channel count * @return The number of samples in the buffer whose absolute value is > 1.0 */ int count_clipped_samples(int channel) const { assert(channel < _channel_count); int clip_count = 0; const float* data = _buffer + size * channel; for (int i = 0 ; i < size; ++i) { /* std::abs() is more efficient than testing for upper and lower bound separately And GCC can compile this to vectorised, branchless code */ clip_count += std::abs(data[i]) >= 1.0f; } return clip_count; } /** * @brief Calculate the peak value / loudest sample for one channel * @param channel The channel to analyse, must not exceed the buffer's channel count * @return The absolute value of the loudest sample */ float calc_peak_value(int channel) const { assert(channel < _channel_count); float max = 0.0f; const float* data = _buffer + size * channel; for (int i = 0 ; i < size; ++i) { max = std::max(max, std::abs(data[i])); } return max; } /** * @brief Calculate the root-mean-square average for one channel * @param channel The channel to analyse, must not exceed the buffer's channel count * @return The RMS value of all the samples in the channel */ float calc_rms_value(int channel) const { assert(channel < _channel_count); float sum = 0.0f; const float* data = _buffer + size * channel; for (int i = 0 ; i < size; ++i) { float s = data[i]; sum += s * s; } return std::sqrt(sum / AUDIO_CHUNK_SIZE); } private: int _channel_count; bool _own_buffer; float* _buffer; friend void swap<>(SampleBuffer& lhs, SampleBuffer& rhs); }; typedef SampleBuffer ChunkSampleBuffer; } // end namespace sushi #endif // SUSHI_SAMPLEBUFFER_H ================================================ FILE: include/sushi/standalone_factory.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Public Sushi factory for standalone use. * @copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef STANDALONE_FACTORY_H #define STANDALONE_FACTORY_H #include "factory_interface.h" #include "sushi.h" namespace sushi { namespace internal { class StandaloneFactoryImplementation; } class StandaloneFactory : public FactoryInterface { public: StandaloneFactory(); ~StandaloneFactory() override; [[nodiscard]] std::pair, Status> new_instance(SushiOptions& options) override; private: std::unique_ptr _implementation; }; } // end namespace sushi #endif // STANDALONE_FACTORY_H ================================================ FILE: include/sushi/sushi.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Main entry point to Sushi * @copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_SUSHI_INTERFACE_H #define SUSHI_SUSHI_INTERFACE_H #include #include #include #include #include #include #include "compile_time_settings.h" namespace sushi { namespace control { class SushiControl; } namespace internal::engine { class AudioEngine; } namespace internal::audio_frontend { struct BaseAudioFrontendConfiguration; class BaseAudioFrontend; } namespace internal::midi_frontend { class BaseMidiFrontend; class ReactiveMidiFrontend; } enum class FrontendType { OFFLINE, DUMMY, JACK, PORTAUDIO, APPLE_COREAUDIO, XENOMAI_RASPA, REACTIVE, NONE }; enum class ConfigurationSource : int { NONE = 0, FILE = 1, JSON_STRING = 2 }; /** * The status of why starting Sushi failed. * The non-zero values are also returned by the process on exit */ enum class Status : int { OK = 0, FAILED_INVALID_FILE_PATH = 1, FAILED_INVALID_CONFIGURATION_FILE = 2, FAILED_LOAD_HOST_CONFIG = 3, FAILED_LOAD_TRACKS = 4, FAILED_LOAD_MIDI_MAPPING = 5, FAILED_LOAD_CV_GATE = 6, FAILED_LOAD_PROCESSOR_STATES = 7, FAILED_LOAD_EVENT_LIST = 8, FAILED_LOAD_EVENTS = 9, FAILED_LOAD_OSC = 10, FAILED_XENOMAI_INITIALIZATION = 11, FAILED_OSC_FRONTEND_INITIALIZATION = 12, FAILED_AUDIO_FRONTEND_MISSING = 13, FAILED_AUDIO_FRONTEND_INITIALIZATION = 14, FAILED_MIDI_FRONTEND_INITIALIZATION = 15, FAILED_TO_START_RPC_SERVER = 16, FRONTEND_IS_INCOMPATIBLE_WITH_STANDALONE = 17, SUSHI_ALREADY_STARTED = 18, SUSHI_THREW_EXCEPTION = 19, UNINITIALIZED = 20 }; std::string to_string(Status status); /** * Collects all options for instantiating Sushi in one place. */ struct SushiOptions { /** * Set this to choose what audio frontend type Sushi should use. * The options are defined in the FrontendType enum class. */ FrontendType frontend_type = FrontendType::NONE; /** * Specify a directory to be the base of plugin paths used in JSON configuration files, * and over gRPC commands for plugin loading. */ std::string base_plugin_path = std::filesystem::current_path().string(); /** * Set this to choose how Sushi will be configured: * By a json-config file path (ConfigurationSource::FILE), * a string containing a json configuration (ConfigurationSource::JSON_STRING), * or no configuration (ConfigurationSource::NONE) * - in which case the minimum default config is set. */ ConfigurationSource config_source = ConfigurationSource::FILE; /** * Only used if config_source is set to ConfigurationSource::FILE. */ std::string config_filename = std::string(SUSHI_JSON_FILENAME_DEFAULT); /** * Only used if config_source is set to ConfigurationSource::JSON_STRING. */ std::string json_string = std::string(SUSHI_JSON_STRING_DEFAULT); /** * Specify minimum logging level, from ('debug', 'info', 'warning', 'error') */ std::string log_level = std::string(ELKLOG_LOG_LEVEL_DEFAULT); /** * Specify logging file destination. */ std::string log_file = std::string(ELKLOG_LOG_FILE_DEFAULT); /** * These are only for the case of using a JACK audio frontend, * and even then, you will rarely need to alter the defaults. */ std::string jack_client_name = std::string(SUSHI_JACK_CLIENT_NAME_DEFAULT); std::string jack_server_name = std::string(""); /** * Try to automatically connect Jack ports at startup. */ bool connect_ports = false; /** * Index of the device to use for audio input with portaudio frontend [default=system default]. */ std::optional portaudio_input_device_id = std::nullopt; /** * Index of the device to use for audio output with portaudio frontend [default=system default] */ std::optional portaudio_output_device_id = std::nullopt; /** * UID of the device to use for audio input with Apple CoreAudio frontend. */ std::optional apple_coreaudio_input_device_uid = std::nullopt; /** * UID of the device to use for audio output with Apple CoreAudio frontend. */ std::optional apple_coreaudio_output_device_uid = std::nullopt; /** * Input latency in seconds to suggest to portaudio. * Will be rounded up to closest available latency depending on audio API. */ float suggested_input_latency = SUSHI_PORTAUDIO_INPUT_LATENCY_DEFAULT; /** * Output latency in seconds to suggest to portaudio. * Will be rounded up to closest available latency depending on audio API. */ float suggested_output_latency = SUSHI_PORTAUDIO_OUTPUT_LATENCY_DEFAULT; /** * If true, Sushi will dump available audio devices to stdout in JSON format, and immediately exit. * This requires a frontend to be specified. */ bool enable_audio_devices_dump = false; /** * Dump plugin and parameter data to stdout in JSON format. * This will reflect the configuration currently loaded. */ bool enable_parameter_dump = false; /** * Set this to false, to disable Open Sound Control completely. */ bool use_osc = true; /** * If the OSC control frontend is enabled, * you will also need to configure the IP and Ports it uses. * * * The osc_server_port is the Port to listen for OSC messages on. * If it is unavailable, Sushi will fail to start, returning: * Status::FAILED_OSC_FRONTEND_INITIALIZATION. */ int osc_server_port = SUSHI_OSC_SERVER_PORT_DEFAULT; /** * Port and IP to send OSC messages to. */ int osc_send_port = SUSHI_OSC_SEND_PORT_DEFAULT; std::string osc_send_ip = SUSHI_OSC_SEND_IP_DEFAULT; /** * Set this to false, to disable gRPC completely. */ bool use_grpc = true; /** * gRPC listening address in the format: address:port. By default accepts incoming connections from all ip:s. */ std::string grpc_listening_address = SUSHI_GRPC_LISTENING_PORT_DEFAULT; /** * Set the path to the crash handler to use for sentry reports. */ std::string sentry_crash_handler_path = SUSHI_SENTRY_CRASH_HANDLER_PATH_DEFAULT; /** * Set the DSN that sentry should upload crash logs to. */ std::string sentry_dsn = SUSHI_SENTRY_DSN_DEFAULT; /** * These are used only if Sushi uses an Offline audio frontend. * Then, sushi uses the first path as its audio input, * and the second as output. */ std::string input_filename; std::string output_filename; /** * Break to debugger if a mode switch is detected (Xenomai only). */ bool debug_mode_switches = false; /** * Process audio multi-threaded, with n cores [default n=1 (off)]. */ int rt_cpu_cores = 1; /** * Enable performance timings on all audio processors. */ bool enable_timings = false; /** * Log timings for every audio callback on these processors. Results will be written as a csv file on exit. * Only use for debug purposes, may store significant amounts of data. */ std::vector detailed_timing_log; /** * Enable flushing the log periodically and specify the interval. */ bool enable_flush_interval = false; std::chrono::seconds log_flush_interval = std::chrono::seconds(0); /** * Extracts the address string and port number from the grpc_listening_address string. * @return A pair with address and port on success - nullopt on failure. */ std::optional> grpc_address_and_port(); /** * If sushi is to be started with gRPC, initialising it requires a valid gRPC port number. * Using this method, it is possible to incrementally increase the number passed, * to retry connecting. * @param options the SushiOptions structure containing the gRPC address field. * @return true if incrementing the value succeeded. */ bool increment_grpc_port_number(); // This field is used internally by Sushi. std::optional device_name = std::nullopt; }; /** * base Sushi class API. * To create a Sushi instance, use one of the factories provided, depending on the use-case required: * - ReactiveFactory * - StandaloneFactory * - OfflineFactory */ class Sushi { protected: Sushi() = default; public: virtual ~Sushi() = default; /** * Given Sushi is initialized successfully, call this before the audio callback is first invoked. * This is only meant to be called once during the instance lifetime. * @return Status will reflect if starting was successful, or what error occurred. */ [[nodiscard]] virtual Status start() = 0; /** * Call to stop the Sushi instance. * This is only meant to be called once during the instance lifetime. */ virtual void stop() = 0; /** * @return an instance of the Sushi controller - assuming Sushi has first been initialized. */ virtual control::SushiControl* controller() = 0; /** * Setting the sample rate. * @param sample_rate */ virtual void set_sample_rate(float sample_rate) = 0; /** * Querying the currently set sample-rate. * @return */ virtual float sample_rate() const = 0; }; } // end namespace sushi #endif // SUSHI_SUSHI_INTERFACE_H ================================================ FILE: include/sushi/sushi_time.h ================================================ /* * Copyright 2017-2023 Elk Audio AB * * SUSHI is free software: you can redistribute it and/or modify it under the terms of * the GNU Affero General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * SUSHI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with * SUSHI. If not, see http://www.gnu.org/licenses/ */ /** * @brief Sushi time types and constants * @Copyright 2017-2023 Elk Audio AB, Stockholm */ #ifndef SUSHI_TIME_H #define SUSHI_TIME_H #include namespace sushi { /** * @brief Type used for timestamps with micro second granularity */ typedef std::chrono::microseconds Time; /** * @brief Convenience shorthand for setting timestamp to 0, i.e. process event without delay. */ constexpr Time IMMEDIATE_PROCESS = std::chrono::microseconds(0); /** * @brief Get the current time, only for calling from the non-rt part. * @return A Time object containing the current time */ inline Time get_current_time() { return std::chrono::duration_cast