diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1dc1bfd4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "gml"] + path = gml + url = https://github.com/ilmola/gml.git diff --git a/.travis.yml b/.travis.yml index 6b8f4748..ad267628 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,10 +37,11 @@ matrix: addons: apt: sources: - - ubuntu-toolchain-r-test + - ubuntu-toolchain-r-test # only needed for <= trusty - llvm-toolchain-trusty-7 packages: - clang-7 + - libstdc++-8-dev # only needed for <= trusty dist: trusty env: - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7 && COMPILE_ASIO=1" @@ -49,10 +50,11 @@ matrix: addons: apt: sources: - - ubuntu-toolchain-r-test + - ubuntu-toolchain-r-test # only needed for <= trusty - llvm-toolchain-trusty-6.0 packages: - clang-6.0 + - libstdc++-8-dev # only needed for <= trusty dist: trusty env: - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0 && COMPILE_ASIO=1" @@ -61,23 +63,14 @@ matrix: addons: apt: sources: + - ubuntu-toolchain-r-test # only needed for <= trusty - llvm-toolchain-trusty-5.0 packages: - clang-5.0 + - libstdc++-7-dev # only needed for <= trusty dist: trusty env: - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0 && COMPILE_ASIO=1" - # linux with clang 4.0 - - os: linux - addons: - apt: - sources: - - llvm-toolchain-trusty-4.0 - packages: - - clang-4.0 - dist: trusty - env: - - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0 && COMPILE_ASIO=1" # osx with xcode10.1/clang - os: osx osx_image: xcode10.1 diff --git a/configure.ac b/configure.ac index 44570098..7ff63f73 100644 --- a/configure.ac +++ b/configure.ac @@ -227,15 +227,19 @@ AS_IF([test x$have_debugging = xyes], dnl overwrite default CXXFLAGS set by AC_PROG_CXX AS_IF([test x$usercxxflags = xno], [CXXFLAGS="-g"]) -dnl select C++11 (unconditionally) -CXXFLAGS="$CXXFLAGS -std=c++11" -AC_MSG_CHECKING([if $CXX supports "-std=c++11"]) +dnl select C++17 (unconditionally) +CXXFLAGS="$CXXFLAGS -std=c++17" +AC_MSG_CHECKING([if $CXX supports "-std=c++17"]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], AC_MSG_RESULT([yes]), [ AC_MSG_RESULT([no]) - AC_MSG_ERROR([C++11 not supported! Please upgrade.]) + AC_MSG_ERROR([C++17 not supported! Please upgrade.]) ]) +dnl JACK1 problem with C++17: https://github.com/jackaudio/jack1/issues/84 +AS_IF([test x$GCC = xyes], + [WARNING_FLAGS="$WARNING_FLAGS -Wno-register"]) + dnl see http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1123 dnl and http://stackoverflow.com/q/11497252/500098 AC_MSG_CHECKING([if $CXX implements core/1123]) diff --git a/flext/package.txt b/flext/package.txt index de1b22b1..c90b6719 100644 --- a/flext/package.txt +++ b/flext/package.txt @@ -2,8 +2,8 @@ vpath %.cpp ../src -SSR_SRCS = position.cpp orientation.cpp directionalpoint.cpp \ - ssr_global.cpp xmlparser.cpp +SSR_SRCS = legacy_position.cpp legacy_orientation.cpp \ + legacy_directionalpoint.cpp ssr_global.cpp xmlparser.cpp SRCS = $(MAIN_SOURCE) $(SSR_SRCS) INCPATH = -I../apf -I../src @@ -12,7 +12,7 @@ PKG_CONFIG ?= pkg-config INCPATH += `$(PKG_CONFIG) --cflags libxml-2.0` -CXXFLAGS = -std=c++11 +CXXFLAGS = -std=c++17 LIBS = -lfftw3f -lsndfile -lxml2 diff --git a/flext/ssr_flext.h b/flext/ssr_flext.h index 260a48a8..ccff360b 100644 --- a/flext/ssr_flext.h +++ b/flext/ssr_flext.h @@ -31,6 +31,7 @@ #define SSR_FLEXT_H #include +#include #include @@ -50,7 +51,7 @@ FLEXT_NEW_DSP_V("ssr_" #name "~", ssr_ ## name) #include "apf/pointer_policy.h" #include "apf/cxx_thread_policy.h" -#include "../src/source.h" +#include "../src/legacy_source.h" template class SsrFlext : public flext_dsp @@ -220,7 +221,7 @@ class SsrFlext : public flext_dsp for (size_t i = 0; i < _in_channels; ++i) { - _engine.add_source(); + _source_ids.push_back(_engine.add_source("")); AddInSignal(); } @@ -244,6 +245,15 @@ class SsrFlext : public flext_dsp _engine.audio_callback(Blocksize(), InSig(), OutSig()); } + std::string _get_string_id(int numeric_id) const + { + if (numeric_id < 1 || _source_ids.size() < numeric_id) + { + return {}; + } + return _source_ids[numeric_id - 1]; + } + FLEXT_CALLBACK_A(_handle_messages) void _handle_messages(const t_symbol* s, int argc, const t_atom* argv) { @@ -265,7 +275,7 @@ class SsrFlext : public flext_dsp return; } - auto* source = _engine.get_source(src_id); + auto* source = _engine.get_source(_get_string_id(src_id)); if (!source) { @@ -364,14 +374,14 @@ class SsrFlext : public flext_dsp error("%s - src model expects a string value!", thisName()); return; } - Source::model_t model = Source::unknown; + LegacySource::model_t model = LegacySource::unknown; if (!apf::str::S2A(model_str, model)) { error("%s - couldn't convert model string: %s" , thisName(), model_str.c_str()); return; } - source->model = model; + source->model = model == LegacySource::plane ? "plane" : "point"; } else { @@ -506,6 +516,7 @@ class SsrFlext : public flext_dsp int _in_channels; Renderer _engine; + std::vector _source_ids; }; #endif diff --git a/gml b/gml new file mode 160000 index 00000000..154c1d16 --- /dev/null +++ b/gml @@ -0,0 +1 @@ +Subproject commit 154c1d16e334c604a86f0998da4785006e6d22e9 diff --git a/mex/Makefile b/mex/Makefile index 249c9d6e..9ef9dc89 100644 --- a/mex/Makefile +++ b/mex/Makefile @@ -6,7 +6,8 @@ MEXFILES ?= ssr_dca ssr_binaural ssr_vbap ssr_aap ssr_wfs ssr_generic \ ssr_brs -OBJECTS := ssr_global position orientation directionalpoint xmlparser +OBJECTS := ssr_global legacy_position legacy_orientation \ + legacy_directionalpoint xmlparser LIBRARIES += libxml-2.0 fftw3f sndfile @@ -15,7 +16,7 @@ SRC_DIR ?= ../src MEX ?= mex -v OCT ?= mkoctfile --mex --verbose -CXXFLAGS += -std=c++11 +CXXFLAGS += -std=c++17 # optimization: CXXFLAGS_OPT += -O3 diff --git a/mex/ssr_mex.h b/mex/ssr_mex.h index 5101b5c4..f67c3901 100644 --- a/mex/ssr_mex.h +++ b/mex/ssr_mex.h @@ -44,7 +44,7 @@ #include "apf/cxx_thread_policy.h" #include "loudspeakerrenderer.h" -#include "../src/source.h" +#include "../src/legacy_source.h" template class SsrMex @@ -208,6 +208,7 @@ class SsrMex _out_channels = _engine->get_output_list().size(); + assert(_source_ids.size() == 0); for (mwSize i = 0; i < _in_channels; ++i) { apf::parameter_map source_params; @@ -216,8 +217,7 @@ class SsrMex { source_params.set("properties_file", filename_list[i]); } - // TODO: specify ID? - _engine->add_source(source_params); + _source_ids.push_back(_engine->add_source("", source_params)); } _inputs.resize(_in_channels); @@ -367,7 +367,7 @@ class SsrMex APF_MEX_ERROR_NO_FURTHER_INPUTS(command); APF_MEX_ERROR_ONE_OPTIONAL_OUTPUT(command); - std::vector ls_list; + std::vector ls_list; _engine->get_loudspeakers(ls_list); if (command == "loudspeaker_position") @@ -395,6 +395,16 @@ class SsrMex } } + // zero-based index + std::string _get_source_id(mwSize index) + { + if (index < 0 || static_cast(_source_ids.size()) <= index) + { + return {}; + } + return _source_ids[index]; + } + void _source_position(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'source_position'"); @@ -419,7 +429,7 @@ class SsrMex { // TODO: handle 3D coordinates - auto* source = _engine->get_source(i + 1); + auto* source = _engine->get_source(_get_source_id(i)); // TODO: check if source == nullptr source->position = Position(coordinates[i*2], coordinates[i*2+1]); } @@ -443,7 +453,7 @@ class SsrMex for (mwSize i = 0; i < _in_channels; ++i) { - auto* source = _engine->get_source(i + 1); + auto* source = _engine->get_source(_get_source_id(i)); // TODO: check if source == nullptr source->orientation = Orientation(angles[i]); // degree } @@ -470,7 +480,7 @@ class SsrMex for (mwSize i = 0; i < _in_channels; ++i) { - auto* source = _engine->get_source(i + 1); + auto* source = _engine->get_source(_get_source_id(i)); source->mute = mute[i]; // logical } } @@ -493,13 +503,14 @@ class SsrMex for (int i = 0; i < _in_channels; ++i) { - Source::model_t model = Source::unknown; + LegacySource::model_t model = LegacySource::unknown; if (!apf::str::S2A(model_list[i], model)) { mexPrintf("Model string '%s':", model_list[i].c_str()); mexErrMsgTxt("Couldn't convert source model string!"); } - _engine->get_source(i + 1)->model = model; + _engine->get_source(_get_source_id(i))->model + = model == LegacySource::plane ? "plane" : "point"; } } @@ -549,6 +560,7 @@ class SsrMex std::unique_ptr _engine; mwSize _in_channels, _out_channels, _block_size; std::vector _inputs, _outputs; + std::vector _source_ids; }; #endif diff --git a/src/Doxyfile b/src/Doxyfile index 0fb0d382..43c64831 100644 --- a/src/Doxyfile +++ b/src/Doxyfile @@ -99,7 +99,7 @@ ALWAYS_DETAILED_SEC = YES # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. -INLINE_INHERITED_MEMB = NO +INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set @@ -534,7 +534,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = main.cpp . +INPUT = main.cpp . ../gml/include/gml # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/src/Makefile.am b/src/Makefile.am index bf160804..94a738f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,6 +23,9 @@ AM_CPPFLAGS = ## Add Audio Processing Framework (APF) path AM_CPPFLAGS += -I$(srcdir)/../apf +## Add GML path +AM_CPPFLAGS += -I$(srcdir)/../gml/include + if ENABLE_APP_BUNDLE AM_CPPFLAGS += -DSSR_DATA_DIR=\"SoundScapeRenderer.app/Contents/Resources\" else @@ -41,7 +44,9 @@ DOXYGEN_DOC_DIR = ../doc/doxygen # files which should be distributed but not installed dist_noinst_DATA = Doxyfile coding_style.txt \ ../apf/misc/Makefile.dependencies \ - ../apf/apf/mextools.h + ../apf/apf/mextools.h \ + ../gml/README.md \ + ../gml/LICENSE.txt ssr_binaural_SOURCES = ssr_binaural.cpp binauralrenderer.h \ $(SSRSOURCES) @@ -88,7 +93,7 @@ nodist_ssr_dca_SOURCES = $(SSRMOCFILES) LOUDSPEAKERSOURCES = \ loudspeakerrenderer.h \ - loudspeaker.h + legacy_loudspeaker.h SSRSOURCES = \ ../apf/apf/blockdelayline.h \ @@ -114,25 +119,29 @@ SSRSOURCES = \ ../apf/apf/shareddata.h \ ../apf/apf/sndfiletools.h \ ../apf/apf/stringtools.h \ + ../gml/include/gml/vec.hpp \ + ../gml/include/gml/quaternion.hpp \ configuration.cpp \ configuration.h \ controller.h \ - directionalpoint.cpp \ - directionalpoint.h \ + legacy_directionalpoint.cpp \ + legacy_directionalpoint.h \ maptools.h \ - orientation.cpp \ - orientation.h \ - position.cpp \ - position.h \ + legacy_orientation.cpp \ + legacy_orientation.h \ + legacy_position.cpp \ + legacy_position.h \ posixpathtools.h \ - publisher.h \ + api.h \ + geometry.h \ rendererbase.h \ - scene.cpp \ + legacy_scene.cpp \ + legacy_scene.h \ + legacy_xmlsceneprovider.h \ scene.h \ - source.h \ + legacy_source.h \ ssr_global.cpp \ ssr_global.h \ - subscriber.h \ timetools.h \ tracker.h \ xmlparser.cpp \ diff --git a/src/aaprenderer.h b/src/aaprenderer.h index 968ec813..d677ef49 100644 --- a/src/aaprenderer.h +++ b/src/aaprenderer.h @@ -183,7 +183,7 @@ AapRenderer::load_reproduction_setup() for (const auto& out: rtlist_proxy(this->get_output_list())) { - if (out.model == Loudspeaker::subwoofer) + if (out.model == LegacyLoudspeaker::subwoofer) { // TODO: something } @@ -221,7 +221,7 @@ AapRenderer::RenderFunction::select(SourceChannel& in) auto weighting_factor = sample_type(); - if (_out.model == Loudspeaker::normal) + if (_out.model == LegacyLoudspeaker::normal) { // WARNING: The reference offset is currently broken! diff --git a/src/api.h b/src/api.h new file mode 100644 index 00000000..ed626134 --- /dev/null +++ b/src/api.h @@ -0,0 +1,455 @@ +/****************************************************************************** + * Copyright © 2019 SSR Contributors * + * * + * This file is part of the SoundScape Renderer (SSR). * + * * + * The SSR is free software: you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * The SSR 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program. If not, see . * + * * + * The SSR is a tool for real-time spatial audio reproduction providing a * + * variety of rendering algorithms. * + * * + * http://spatialaudio.net/ssr ssr@spatialaudio.net * + ******************************************************************************/ + +/// @file +/// API definition for all functions of the SSR. + +#ifndef SSR_API_H +#define SSR_API_H + +#include // for uint32_t +#include // for std::unique_ptr +#include +#include // for std::pair +#include + +namespace ssr +{ + +using id_t = const std::string&; + +/// 3D vector type for positions and translations. @see vec3, Rot +struct Pos { float x{}, y{}, z{}; }; +/// Quaternion type for rotations. @see quat, Pos +struct Rot { float x{}, y{}, z{}, w{1}; }; + +/// Information about a single loudspeaker. +struct Loudspeaker +{ + Pos position; + Rot rotation; + + /// Type of loudspeaker. Normal loudspeakers use the empty string @c "", + /// subwoofers use @c "subwoofer". + /// Other types might be supported by some renderers. + std::string model; +}; + +/// Interface for controlling scene and renderer. +/// None of the classes/functions here is thread-safe, i.e. it is not allowed to +/// access/call them from different threads at the same time. +/// The Publisher::take_control() and Publisher::update_follower() functions +/// take exclusive control over the SSR instance, by acquiring a mutex. +namespace api +{ + +/// Bundle start/stop events. Bundles typically contain one or more messages +/// that are supposed to happen at the same time. +/// @see Publisher::subscribe_bundle() +struct BundleEvents +{ + virtual ~BundleEvents() = default; + + /// A bundle has been started. For example by Publisher::take_control() or + /// Publisher::update_follower(). + virtual void bundle_start() = 0; + + /// A bundle has been finished. + virtual void bundle_stop() = 0; +}; + + +/// Subscribable scene properties that can be changed by clients. +/// @see Publisher::subscribe_scene_control() +struct SceneControlEvents +{ + virtual ~SceneControlEvents() = default; + + /// Orient sources towards the reference point ... or not. + /// @todo additional per-source setting? + virtual void auto_rotate_sources(bool auto_rotate) = 0; + + /// Remove a source from the Scene. + /// @param id ID of the source + virtual void delete_source(id_t id) = 0; + + /// Set source position. + /// @param id ID of the source + /// @param position new position + virtual void source_position(id_t id, const Pos& position) = 0; + + /// Set source rotation/orientation. + /// @param id ID of the source + /// @param rotation new rotation + virtual void source_rotation(id_t id, const Rot& rotation) = 0; + + /// Set source volume. + /// @param id ID of the source + /// @param volume new volume (linear) + virtual void source_volume(id_t id, float volume) = 0; + + /// Mute/unmute source. + /// @param id ID of the source + /// @param mute mute if @b true, unmute if @b false + virtual void source_mute(id_t id, bool mute) = 0; + + /// Set source name. + /// @param id ID of the source + /// @param name new name + virtual void source_name(id_t id, const std::string& name) = 0; + + /// Set source model (=type). This is typically @c "point" or @c "plane", + /// but certain renderers might support more models. + /// @param id ID of the source + /// @param model new model + virtual void source_model(id_t id, const std::string& model) = 0; + + /// Set whether source position/rotation is fixed. + /// @param id ID of the source + /// @param fixed whether position/rotation is fixed or not + virtual void source_fixed(id_t id, bool fixed) = 0; + + /// Set reference position. + /// @param position new position + virtual void reference_position(const Pos& position) = 0; + + /// Set reference rotation/orientation. + /// @param rotation new rotation + virtual void reference_rotation(const Rot& rotation) = 0; + + /// Set master volume. + /// @param volume new volume (linear) + virtual void master_volume(float volume) = 0; + + /// Set amplitude decay exponent. + /// @param exponent amplitude decay exponent + /// @todo per source setting? + virtual void decay_exponent(float exponent) = 0; + + /// Set amplitude reference distance. + /// @param distance amplitude reference distance + virtual void amplitude_reference_distance(float distance) = 0; +}; + + +/// Subscribable scene information that cannot be directly changed by clients. +/// @see Publisher::subscribe_scene_information() +struct SceneInformationEvents +{ + virtual ~SceneInformationEvents() = default; + + /// Publish creation of a new source. + /// @param id ID of the new source + virtual void new_source(id_t id) = 0; + + /// Set immutable properties (string-to-string mapping) of a (new) source, + /// e.g. @c audio_file, @c audio_file_channel, @c audio_file_length, + /// @c port_name, @c properties_file. + virtual void source_property(id_t id, const std::string& key + , const std::string& value) = 0; +}; + + +/// Subscribable renderer properties that can be changed by clients. +/// @see Publisher::subscribe_renderer_control() +struct RendererControlEvents +{ + virtual ~RendererControlEvents() = default; + + /// Switch processing on/off + virtual void processing(bool processing_state) = 0; + + /// Renderer-specific position offset. + /// Relative to SceneControlEvents::reference_position(). + virtual void reference_offset_position(const Pos& position) = 0; + + /// Renderer-specific rotation offset. + /// Relative to SceneControlEvents::reference_rotation(). + /// This is typically controlled by a head tracker. + virtual void reference_offset_rotation(const Rot& rotation) = 0; +}; + + +/// Subscribable renderer properties that cannot be changed by clients. +/// @see Publisher::subscribe_renderer_information() +struct RendererInformationEvents +{ + virtual ~RendererInformationEvents() = default; + + /// Name of the renderer. + virtual void renderer_name(const std::string& name) = 0; + + /// Sampling rate of the renderer in Hertz. + virtual void sample_rate(int rate) = 0; + + /// List of loudspeakers. Doesn't change during the lifetime of a renderer. + virtual void loudspeakers(const std::vector& loudspeakers) = 0; +}; + + +/// Continuous updates about the current JACK transport state. +/// This is part of the scene information but because of the high frequency of +/// messages this can be subscribed separately. +/// @see Publisher::subscribe_transport() +struct TransportEvents +{ + virtual ~TransportEvents() = default; + + /// Update transport state. + /// @param rolling whether transport is currently rolling or stopped + /// @param frame current transport time in frames + /// @see http://www.jackaudio.org/files/docs/html/group__TransportControl.html + virtual void transport_state(bool rolling, uint32_t frame) = 0; +}; + + +/// Continuous updates about current source signal levels. +/// This is part of the scene information but because of the high frequency of +/// messages this can be subscribed separately. +/// @see Publisher::subscribe_source_metering() +struct SourceMetering +{ + virtual ~SourceMetering() = default; + + /// Instantaneous level of source signal. + /// @param id ID of the source + /// @param level level (linear scale) + virtual void source_level(id_t id, float level) = 0; +}; + + +/// Continuous updates about the current master signal level. +/// This is part of the renderer information but because of the high frequency +/// of messages this can be subscribed separately. +/// @see Publisher::subscribe_master_metering() +struct MasterMetering +{ + virtual ~MasterMetering() = default; + + /// Combined (= maximum) instantaneous level of all outputs. + /// @param level level (linear scale) + virtual void master_level(float level) = 0; +}; + + +/// Continuous updates about output activity, per source. +/// This is part of the renderer information but because of the high frequency +/// of messages this can be subscribed separately. +/// @see Publisher::subscribe_output_activity() +struct OutputActivity +{ + virtual ~OutputActivity() = default; + + /// Influence of a given source on each output (as a linear scaling factor). + /// @param id ID of the source + /// @param begin Pointer to first activity + /// @param end Past-the-end pointer + virtual void output_activity(id_t id, float* begin, float* end) = 0; +}; + + +/// Continuous updates about the renderer's CPU load (as reported by JACK). +/// This is part of the renderer information but because of the high frequency +/// of messages this can be subscribed separately. +/// @see Publisher::subscribe_cpu_load() +struct CpuLoad +{ + virtual ~CpuLoad() = default; + + /// CPU load. + /// @param load CPU load in percent + virtual void cpu_load(float load) = 0; +}; + + +/// Interface for controlling an SSR instance. +/// Its renderer can be controlled with RendererControlEvents. +/// If the instance is itself a "follower", the SceneControlEvents are +/// forwarded to the connected "leader" instance. +/// Control over an SSR instance can be obtained with Publisher::take_control(). +struct Controller : virtual SceneControlEvents + , RendererControlEvents +{ + /// Load a scene from a file. + virtual void load_scene(const std::string& filename) = 0; + + /// Save a scene to a file. + virtual void save_scene(const std::string& filename) const = 0; + + /// Create a new source. + /// @param id Unique identifier of the source. If a source with that ID + /// already exists, the source will not be created. If an empty string is + /// given, a unique ID will be auto-generated. + /// @param name Name of the new source. This doesn't have to be unique and it + /// can change during the existence of the source. + /// @param model Model (=type) of the new source. Default is @c "point". + /// @param file_or_port_name Name of audio file or JACK port. + /// If a JACK port is given, @a channel has to be zero! + /// @param channel number of channel in audio file. + /// If zero, a JACK port is given instead of an audio file. + /// @param position Initial position of the new source. + /// @param rotation Initial rotation of the new source. + /// @param fixed Whether the source can be moved or not. + /// @param volume Initial (linear) volume of the new source. + /// @param mute Initial mute state. + /// @param properties_file Renderer-specific additional file, e.g. containing + /// impulse responses. Use empty string if not needed. + virtual void new_source(const std::string& id, const std::string& name + , const std::string& model, const std::string& file_name_or_port_number + , int channel, const Pos& position, const Rot& rotation, bool fixed + , float volume, bool mute, const std::string& properties_file) = 0; + + /// Delete all sources. + virtual void delete_all_sources() = 0; + + /// Start JACK transport. @see TransportEvents + virtual void transport_start() = 0; + + /// Stop JACK transport. @see TransportEvents + virtual void transport_stop() = 0; + + /// Skip the scene to a specified instant of time. + /// @param time instant of time in seconds. + /// @see TransportEvents + virtual void transport_locate(float time) = 0; + + /// Reset the tracker (if one is connected). + /// @todo There should probably be a more fancy tracker interface ...? + virtual void calibrate_tracker() = 0; + + /// Get string ID given one-based source number. + /// If the source number is @c 0 or higher than the number of sources, an + /// empty string is returned. + /// + /// This is meant to help implementing clients that don't track source IDs. + /// If they (or their human operator) have a-priori knowledge about the + /// current number of sources, this function can be used to get the + /// corresponding IDs. + /// @note The source numbers change when sources are removed! + /// @see Publisher::get_source_number() + virtual std::string get_source_id(unsigned int source_number) const = 0; +}; + + +/// Messages sent from the "leader" instance to a "follower". +/// Control over a "follower" can be obtained with Publisher::update_follower(). +struct Follower : virtual SceneControlEvents + , SceneInformationEvents + , TransportEvents + , SourceMetering +{}; + + +/// A subscription to a group of events. +/// Nothing interesting can be done with a Subscription object, but when it is +/// destroyed, the corresponding subscription is automatically cancelled. +/// @see Publisher +struct Subscription +{ + virtual ~Subscription() = default; +}; + + +/// Handle subscriptions, allow controlling the scene/renderer. +struct Publisher +{ + virtual ~Publisher() = default; + + /// Subscribe to BundleEvents. + /// A subscriber must outlive its subscription. This can be easily ensured + /// by binding the returned Subscription to the scope of the subscriber, + /// because the subscriber is automatically unsubscribed when the subscription + /// object goes out of scope. + virtual std::unique_ptr subscribe_bundle( + BundleEvents* subscriber) = 0; + /// Subscribe to SceneControlEvents. + /// Additionally, this sends the current state to the @a subscriber. + /// @see subscribe_bundle() + virtual std::unique_ptr subscribe_scene_control( + SceneControlEvents* subscriber) = 0; + /// Subscribe to SceneInformationEvents. + /// Additionally, this sends the current state to the @a subscriber. + /// @see subscribe_bundle() + virtual std::unique_ptr subscribe_scene_information( + SceneInformationEvents* subscriber) = 0; + /// Subscribe to RendererControlEvents. + /// Additionally, this sends the current state to the @a subscriber. + /// @see subscribe_bundle() + virtual std::unique_ptr subscribe_renderer_control( + RendererControlEvents* subscriber) = 0; + /// Subscribe to RendererInformationEvents. + /// Additionally, this sends the current state to the @a subscriber. + /// @see subscribe_bundle() + virtual std::unique_ptr subscribe_renderer_information( + RendererInformationEvents* subscriber) = 0; + /// Subscribe to TransportEvents. @see subscribe_bundle() + virtual std::unique_ptr subscribe_transport( + TransportEvents* subscriber) = 0; + /// Subscribe to SourceMetering. @see subscribe_bundle() + virtual std::unique_ptr subscribe_source_metering( + SourceMetering* subscriber) = 0; + /// Subscribe to MasterMetering. @see subscribe_bundle() + virtual std::unique_ptr subscribe_master_metering( + MasterMetering* subscriber) = 0; + /// Subscribe to OutputActivity. @see subscribe_bundle() + virtual std::unique_ptr subscribe_output_activity( + OutputActivity* subscriber) = 0; + /// Subscribe to CpuLoad. @see subscribe_bundle() + virtual std::unique_ptr subscribe_cpu_load( + CpuLoad* subscriber) = 0; + + /// Subscribe to "leader" (if this Publisher is a "follower"). + virtual std::unique_ptr subscribe_leader( + Controller* leader) = 0; + + /// Obtain exclusive control over this Publisher's SSR instance. + /// This automatically starts a "bundle", see BundleEvents. + /// When the returned object is destroyed, the "bundle" is closed and + /// exclusive control is automatically released. + /// @param suppress_own The generated SceneControlEvents are not forwarded to + /// the given subscriber. + virtual std::unique_ptr take_control( + SceneControlEvents* suppress_own) = 0; + /// @overload + virtual std::unique_ptr take_control() = 0; + + /// Obtain exclusive control over this Publisher's SSR instance, which is + /// supposed to be a "follower". + /// This automatically starts a "bundle", see BundleEvents. + /// When the returned object is destroyed, the "bundle" is closed and + /// exclusive control is automatically released. + virtual std::unique_ptr update_follower() = 0; + + /// Get the one-based source number given a string ID. + /// If the given ID doesn't exist, @c 0 is returned. + /// @note The source numbers change when sources are removed! + /// @see Controller::get_source_id() + virtual unsigned int get_source_number(id_t source_id) const = 0; +}; + +} // namespace api + +} // namespace ssr + +#endif diff --git a/src/binauralrenderer.h b/src/binauralrenderer.h index c665072e..cc8117a8 100644 --- a/src/binauralrenderer.h +++ b/src/binauralrenderer.h @@ -278,7 +278,7 @@ void BinauralRenderer::Source::_process() float source_distance = (this->position - ref_pos).length(); if (this->weighting_factor != 0 && source_distance < 0.5f - && this->model != ::Source::plane) + && this->model != "plane") { interp_factor = 1.0f - 2 * source_distance; } @@ -291,7 +291,7 @@ void BinauralRenderer::Source::_process() // calculate relative orientation of sound source auto rel_ori = -ref_ori; - if (this->model == ::Source::plane) + if (this->model == "plane") { // plane wave orientation points into direction of propagation // +180 degree has to be applied to select the hrtf correctly diff --git a/src/configuration.cpp b/src/configuration.cpp index d2f92b65..5a250f75 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -143,6 +143,8 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) #endif conf.server_port = 4711; + conf.follow = false; + conf.freewheeling = false; conf.scene_file_name = ""; conf.renderer_params.set("reproduction_setup" @@ -256,6 +258,8 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) " -i, --ip-server Start IP server (not enabled at compile time!)\n" " -I, --no-ip-server Don't start IP server (default)\n" #endif +" --follow Wait for another SSR instance to connect\n" +" --no-follow Don't follow another SSR instance (default)\n" #ifdef ENABLE_GUI " -g, --gui Start GUI (default)\n" " -G, --no-gui Don't start GUI\n" @@ -329,6 +333,8 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) {"ip-server", optional_argument, nullptr, 'i'}, {"no-ip-server", no_argument, nullptr, 'I'}, {"end-of-message-character", required_argument, nullptr, 0}, + {"follow", no_argument, nullptr, 0 }, + {"no-follow", no_argument, nullptr, 0 }, {"gui", no_argument, nullptr, 'g'}, {"no-gui", no_argument, nullptr, 'G'}, {"tracker", required_argument, nullptr, 't'}, @@ -415,6 +421,14 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) ERROR("Invalid end-of-message character specified!"); } } + else if (strcmp("follow", longopts[longindex].name) == 0) + { + conf.follow = true; + } + else if (strcmp("no-follow", longopts[longindex].name) == 0) + { + conf.follow = false; + } else if (strcmp("tracker-port", longopts[longindex].name) == 0) { conf.tracker_ports = optarg; @@ -815,6 +829,17 @@ int ssr::load_config_file(const char *filename, conf_struct& conf){ } #endif } + else if (!strcmp(key, "FOLLOW")) + { + if (!strcasecmp(value, "yes")) + { + conf.follow = true; + } + else + { + conf.follow = false; + } + } else if (!strcmp(key, "VERBOSE")) { ssr::verbose = atoi(value); diff --git a/src/configuration.h b/src/configuration.h index a53413e0..ebdac4ed 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -61,6 +61,8 @@ struct conf_struct bool auto_rotate_sources; ///< Automatic orientation of sources int server_port; ///< listening port + /// allow other SSR instance to provide scene data? + bool follow; /// size of delay line (in samples) int wfs_delayline_size; /// maximum negative delay (in samples, wfs_initial_delay >= 0) diff --git a/src/controller.h b/src/controller.h index d5dcff61..068e6f22 100644 --- a/src/controller.h +++ b/src/controller.h @@ -34,7 +34,10 @@ #include // for ENABLE_*, HAVE_*, WITH_* #endif -// TODO: move these includes to a more suitable location? +#include // for std::function +#include // for std::is_same_v +#include + #include "apf/jack_policy.h" #include "apf/cxx_thread_policy.h" @@ -43,7 +46,7 @@ #include // temporary hack! #include "ssr_global.h" -#include "publisher.h" +#include "api.h" #ifdef ENABLE_ECASOUND #include "audioplayer.h" @@ -59,6 +62,7 @@ #ifdef ENABLE_IP_INTERFACE #include "server.h" +#include "legacy_xmlsceneprovider.h" // for LegacyXmlSceneProvider #endif #include "tracker.h" @@ -75,9 +79,11 @@ #include "trackerrazor.h" #endif +#include "legacy_scene.h" // for LegacyScene #include "scene.h" // for Scene -#include "rendersubscriber.h" +#include "rendersubscriber.h" // for RenderSubscriber +#include "geometry.h" // for look_at() #include "posixpathtools.h" #include "apf/math.h" #include "apf/stringtools.h" @@ -110,109 +116,262 @@ inline void print_about_message() } // namespace internal -/** %Controller class. - * Has a list of objects which receive messages on events like position change - * etc. With this list a kind of Publisher/Subscriber pattern is realized. - **/ + +/// %Controller class. Implements the Publisher interface. +/// The Controller can be either a "leader" or a "follower". template -class Controller : public Publisher +class Controller : public api::Publisher +#ifdef ENABLE_IP_INTERFACE + , public LegacyXmlSceneProvider +#endif { public: - using loudspeaker_id_t = Loudspeaker::container_t::size_type; - /// ctor Controller(int argc, char *argv[]); virtual ~Controller(); ///< dtor bool run(); - void set_source_output_levels(id_t id, float* first, float* last); + private: + using output_list_t + = typename Renderer::template rtlist_proxy; - virtual bool load_scene(const std::string& scene_file_name); - virtual bool save_scene_as_XML(const std::string& filename) const; + template class CommonInterface; + template class ControlInterface; + class FollowerInterface; - virtual void start_processing(); - virtual void stop_processing(); + class query_state; - virtual void new_source(const std::string& name, Source::model_t model - , const std::string& file_name_or_port_number, int channel = 0 - , const Position& position = Position(), const bool pos_fix = false - , const Orientation& orientation = Orientation() - , const bool or_fix = false - , const float gain = 1.0f, const bool muted = false - , const std::string& properties_file = ""); + // Ordered list, keeps subscription order + template using Subscribers = std::vector; - virtual void delete_source(id_t id); - /// delete all sources in all subscribers - virtual void delete_all_sources(); + class Subscription : public api::Subscription + { + public: + explicit Subscription(std::function f) : _f(std::move(f)) {} + ~Subscription() { _f(); } - virtual void set_source_position(id_t id, const Position& position); - virtual void set_source_orientation(id_t id - , const Orientation& orientation); - void orient_source_toward_reference(const id_t id); - void orient_all_sources_toward_reference(); - virtual void set_source_gain(id_t id, float gain); - virtual void set_source_signal_level(const id_t id - , const float level); - virtual void set_source_mute(id_t id, bool mute); - virtual void set_source_name(id_t id, const std::string& name); - virtual void set_source_properties_file(id_t id, const std::string& name); - virtual void set_source_model(id_t id, Source::model_t model); - virtual void set_source_port_name(id_t id, const std::string& port_name); - virtual void set_source_file_name(id_t id, const std::string& file_name); - virtual void set_source_file_channel(id_t id, const int& channel); - virtual void set_source_position_fixed(id_t id, const bool fixed); + private: + std::function _f; + }; - virtual void set_reference_position(const Position& position); - virtual void set_reference_orientation(const Orientation& orientation); + struct _noop { void operator()() {} }; - virtual void set_reference_offset_position(const Position& position); - virtual void set_reference_offset_orientation(const Orientation& orientation); + template + std::unique_ptr + _subscribe_helper(Events* subscriber, F&& init_func = F{}) + { + std::lock_guard lock{_m}; + std::forward(init_func)(); + _subscribe(subscriber); + return std::make_unique([this, subscriber]() { + std::lock_guard lock{_m}; + _unsubscribe(subscriber); + }); + } - virtual void set_master_volume(float volume); + std::unique_ptr + subscribe_bundle(api::BundleEvents* subscriber) override + { + return _subscribe_helper(subscriber); + } - virtual void set_decay_exponent(const float exponent); + std::unique_ptr + subscribe_scene_control(api::SceneControlEvents* subscriber) override + { + return _subscribe_helper(subscriber, [this, subscriber]() { + _scene.get_data(subscriber); + }); + } - virtual void set_amplitude_reference_distance(const float dist); + std::unique_ptr + subscribe_scene_information(api::SceneInformationEvents* subscriber) override + { + return _subscribe_helper(subscriber, [this, subscriber]() { + _scene.get_data(subscriber); + }); + } - virtual void set_master_signal_level(float level); + std::unique_ptr + subscribe_renderer_control(api::RendererControlEvents* subscriber) override + { + return _subscribe_helper(subscriber, [this, subscriber]() { + _rendersubscriber.get_data(subscriber); + }); + } - virtual void set_cpu_load(const float load); + std::unique_ptr + subscribe_renderer_information( + api::RendererInformationEvents* subscriber) override + { + return _subscribe_helper(subscriber, [this, subscriber]() { + _rendersubscriber.get_data(subscriber); + }); + } + + std::unique_ptr + subscribe_transport(api::TransportEvents* subscriber) override + { + return _subscribe_helper(subscriber); + } - virtual void publish_sample_rate(const int sample_rate); + std::unique_ptr + subscribe_source_metering(api::SourceMetering* subscriber) override + { + return _subscribe_helper(subscriber); + } - virtual std::string get_renderer_name() const; + std::unique_ptr + subscribe_master_metering(api::MasterMetering* subscriber) override + { + return _subscribe_helper(subscriber); + } + + std::unique_ptr + subscribe_output_activity(api::OutputActivity* subscriber) override + { + return _subscribe_helper(subscriber); + } - virtual bool show_head() const; + std::unique_ptr + subscribe_cpu_load(api::CpuLoad* subscriber) override + { + return _subscribe_helper(subscriber); + } - virtual void transport_start(); - virtual void transport_stop(); - virtual bool transport_locate(float time); + std::unique_ptr + subscribe_leader(api::Controller*) override; - virtual void calibrate_client(); + std::unique_ptr take_control(api::SceneControlEvents*) override; + std::unique_ptr take_control() override; + std::unique_ptr update_follower() override; - /// update JACK transport state - void set_transport_state(const std::pair& state); - /// send processing state of the renderer to all subscribers. - virtual void set_processing_state(bool state); + unsigned int get_source_number(id_t source_id) const override; - virtual void set_auto_rotation(bool auto_rotate_sources); +#ifdef ENABLE_IP_INTERFACE + std::string get_scene_as_XML() const override; +#endif - virtual std::string get_scene_as_XML() const; + bool _load_scene(const std::string& filename); + bool _save_scene(const std::string& filename) const; - virtual void subscribe(Subscriber* subscriber); - virtual void unsubscribe(Subscriber* subscriber); + void _new_source(const std::string& id, const std::string& name + , const std::string& model, const std::string& file_name_or_port_number + , int channel, const Pos& position, const Rot& rotation, bool fixed + , float volume, bool mute, const std::string& properties_file); - void set_loop_mode(bool loop) { _loop = loop; } ///< temporary solution! + void _delete_all_sources(); - // temporary solution! - void deactivate() { _renderer.deactivate(); } + void _orient_source_toward_reference(id_t id); + void _orient_all_sources_toward_reference(); - private: - using output_list_t - = typename Renderer::template rtlist_proxy; + void _calibrate_client(); - class query_state; + void _transport_locate(float time); + + template + void _subscribe(Events* subscriber) + { + assert(subscriber); + auto& list = std::get>(_subscribers); + if (std::find(list.begin(), list.end(), subscriber) == list.end()) + { + list.push_back(subscriber); + } + } + + template + void _unsubscribe(Events* subscriber) + { + assert(subscriber); + auto& list = std::get>(_subscribers); + auto iter = std::find(list.begin(), list.end(), subscriber); + if (iter != list.end()) + { + list.erase(iter); + } + } + + template + void _call_leader(PTMF member_function, Args&&... args) + { + if (_leader) + { + try { (_leader->*member_function)(std::forward(args)...); } + catch (std::exception& e) { ERROR(e.what()); } + } + else + { + WARNING("Instance is configured as \"follower\", " + "but no \"leader\" is connected"); + } + } + + /// Dummy class to select relevant _publish() overload. + struct ToLeaderTag {}; + + /// Low-level publishing function, overload for SceneControlEvents. + /// The first argument is the initiator of the request (or nullptr). + /// The second argument is a pointer to a member function of + /// SceneControlEvents, the rest are arguments to said member function. + // NB: Args must be convertible to FuncArgs, but they don't necessarily have + // to be the same types. + template + void _publish(api::SceneControlEvents* initiator + , R (api::SceneControlEvents::*f)(FuncArgs...), Args&&... args) + { + // TODO: check if sender is allowed to move source? + + for (api::SceneControlEvents* subscriber: + std::get>(_subscribers)) + { + // NB: If initiator is nullptr this is always true: + if (subscriber != initiator) + { + try { (subscriber->*f)(std::forward(args)...); } + catch (std::exception& e) { ERROR(e.what()); } + } + } + } + + /// Overload for SceneControlEvents to be sent to the "leader". + /// The first argument is a dummy argument for selecting this overload. + template + void _publish(ToLeaderTag* + , R (api::SceneControlEvents::*f)(FuncArgs...), Args&&... args) + { + assert(_conf.follow); + try { _call_leader(f, std::forward(args)...); } + catch (std::exception& e) { ERROR(e.what()); } + } + + /// Overload for all events without options to suppress own messages. + /// Those are never sent to the "leader", therefore it is not allowed to use + /// this for SceneControlEvents on a "follower". + template + void _publish(R (C::*f)(FuncArgs...), Args&&... args) + { + // NB: Nothing is sent to the "leader" + + if constexpr (std::is_same_v) + { + // In a "follower", those must use separate overload from above + assert(!_conf.follow); + } + + for (C* subscriber: std::get>(_subscribers)) + { + try { (subscriber->*f)(std::forward(args)...); } + catch (std::exception& e) { ERROR(e.what()); } + } + } + + std::vector _get_loudspeakers() const + { + std::vector loudspeakers; + _renderer.get_loudspeakers(loudspeakers); + return {loudspeakers.begin(), loudspeakers.end()}; + } #ifdef ENABLE_ECASOUND /// load the audio recorder and set it to "record enable" mode. @@ -233,57 +392,38 @@ class Controller : public Publisher conf_struct _conf; Scene _scene; - /// a list of subscribers - using subscriber_list_t = std::vector; - /// list of objects that will be notified on all events - subscriber_list_t _subscribers; + LegacyScene _legacy_scene; + + std::tuple< + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers, + Subscribers + > _subscribers; + api::Controller* _leader = nullptr; #ifdef ENABLE_GUI std::unique_ptr _gui; #endif Renderer _renderer; + RenderSubscriber _rendersubscriber; query_state _query_state; #ifdef ENABLE_ECASOUND AudioRecorder::ptr_t _audio_recorder; ///< pointer to audio recorder AudioPlayer::ptr_t _audio_player; ///< pointer to audio player #endif - std::string _schema_file_name; ///< XML Schema - std::string _input_port_prefix; ///< e.g. alsa_pcm:capture #ifdef ENABLE_IP_INTERFACE std::unique_ptr _network_interface; #endif std::unique_ptr _tracker; - /// check if audio player is running and start it if necessary - bool _audio_player_is_running(); - - /// Publishing function. - /// The first argument is a pointer to a member function of the Subscriber - /// class, the rest are arguments to said member function. - template - inline void _publish(R (Subscriber::*f)(FuncArgs...), Args&&... args) - { - ScopedLock guard(_subscribers_lock); - for (auto& subscriber: _subscribers) - { - (subscriber->*f)(std::forward(args)...); // ignore return value - } - } - - /// helper struct for a source including its source id - struct SourceCopy : public Source - { - typedef std::vector container_t; ///< list of SourceCopys - /// type conversion constructor - SourceCopy(const std::pair& other) - : Source(other.second) // copy ctor of base class - , id(other.first) - {} - - id_t id; ///< unique ID, see Scene::source_map_t - }; - void _add_master_volume(Node& node) const; void _add_transport_state(Node& node) const; void _add_reference(Node& node) const; @@ -301,8 +441,7 @@ class Controller : public Publisher std::unique_ptr> _query_thread; - typename Renderer::Lock _subscribers_lock; - using ScopedLock = typename Renderer::ScopedLock; + std::mutex _m; }; template @@ -311,16 +450,14 @@ Controller::Controller(int argc, char* argv[]) , _argv(argv) , _conf(configuration(_argc, _argv)) , _renderer(_conf.renderer_params) + , _rendersubscriber(_renderer) , _query_state(query_state(*this, _renderer)) - , _tracker(nullptr) - , _loop(false) + , _loop(_conf.loop) // temporary solution { // TODO: signal handling? internal::print_about_message(); - _renderer.load_reproduction_setup(); - #ifndef ENABLE_IP_INTERFACE if (_conf.ip_server) { @@ -355,31 +492,46 @@ Controller::Controller(int argc, char* argv[]) _conf.gui = false; } - // temporary solution: - this->set_loop_mode(_conf.loop); + // NB: we don't need a lock here because nothing is publishing yet - this->subscribe(&_scene); + _subscribe(&_scene); + _subscribe(&_scene); - this->publish_sample_rate(_renderer.sample_rate()); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); + _subscribe(&_legacy_scene); - std::vector loudspeakers; - _renderer.get_loudspeakers(loudspeakers); - _publish(&Subscriber::set_loudspeakers, loudspeakers); + _subscribe(&_rendersubscriber); + _subscribe(&_rendersubscriber); + _subscribe(&_rendersubscriber); + _subscribe(&_rendersubscriber); - // TODO: memory leak, subscriber is never deleted! - auto subscriber = new RenderSubscriber(_renderer); - this->subscribe(subscriber); + // End of initial subscriptions, start publishing ... + + _publish(&api::RendererInformationEvents::renderer_name + , _renderer.name()); + + _renderer.load_reproduction_setup(); + + _publish(&api::RendererInformationEvents::sample_rate + , _renderer.sample_rate()); + + _publish(&api::RendererInformationEvents::loudspeakers, _get_loudspeakers()); #ifdef ENABLE_ECASOUND _load_audio_recorder(_conf.audio_recorder_file_name); #endif - // TODO: allow specification in scene file - this->set_auto_rotation(_conf.auto_rotate_sources); - - if (!this->load_scene(_conf.scene_file_name)) + if (!_conf.follow) { - throw std::runtime_error("Couldn't load scene!"); + // NB: This is ignored in "followers" + this->take_control()->auto_rotate_sources(_conf.auto_rotate_sources); } if (_conf.freewheeling) @@ -398,11 +550,25 @@ Controller::Controller(int argc, char* argv[]) << " and with end-of-message character with ASCII code " << _conf.end_of_message_character << "."); - _network_interface.reset(new Server(*this, _conf.server_port + _network_interface.reset(new Server(*this, *this, _conf.server_port , static_cast(_conf.end_of_message_character))); _network_interface->start(); } #endif // ENABLE_IP_INTERFACE + + if (_conf.follow && _conf.scene_file_name != "") + { + // TODO: if "follower": connect to "leader" before loading scene + throw std::logic_error( + "Loading a scene as \"follower\" is not yet supported"); + } + + auto control = this->take_control(); // Start a bundle for scene loading + + if (_conf.scene_file_name != "" && !_load_scene(_conf.scene_file_name)) + { + throw std::runtime_error("Couldn't load scene!"); + } } template @@ -414,14 +580,18 @@ class Controller::query_state , _renderer(renderer) {} + // NB: This is executed in the audio thread void query() { - _state = _renderer.get_transport_state(); + if (!_controller._conf.follow) + { + _state = _renderer.get_transport_state(); + } _cpu_load = _renderer.get_cpu_load(); auto output_list = output_list_t(_renderer.get_output_list()); - _master_level = typename Renderer::sample_type(); + _master_level = {}; for (const auto& out: output_list) { _master_level = std::max(_master_level, out.get_level()); @@ -440,6 +610,7 @@ class Controller::query_state levels->source_id = source.id; levels->source_level = source.get_level(); + assert(levels->outputs.size() == _renderer.get_output_list().size()); levels->outputs_available = source.get_output_levels( &*levels->outputs.begin(), &*levels->outputs.end()); @@ -454,29 +625,39 @@ class Controller::query_state } } + // NB: This is executed in the control thread void update() { - _controller._publish(&Subscriber::set_transport_state, _state); - _controller.set_cpu_load(_cpu_load); - _controller.set_master_signal_level(_master_level); + auto control = _controller.take_control(); // Scoped bundle + + if (!_controller._conf.follow) + { + _controller._publish(&api::TransportEvents::transport_state + , _state.first, _state.second); + } + _controller._publish(&api::CpuLoad::cpu_load, _cpu_load); + _controller._publish(&api::MasterMetering::master_level, _master_level); if (!_discard_source_levels) { for (auto& item: _source_levels) { - _controller.set_source_signal_level(item.source_id - , item.source_level); + _controller._publish(&api::SourceMetering::source_level + , item.source_id, item.source_level); // TODO: make this a compile-time decision: if (item.outputs_available) { - _controller.set_source_output_levels(item.source_id - , &*item.outputs.begin(), &*item.outputs.end()); + assert(item.outputs.size() == _renderer.get_output_list().size()); + _controller._publish(&api::OutputActivity::output_activity + , item.source_id, &*item.outputs.begin(), &*item.outputs.end()); } } } else { + // NB: The output list should only be accessed by the audio thread. + // Since the number of outputs is never changed, that's OK here. _source_levels.resize(_new_size , SourceLevel(_renderer.get_output_list().size())); } @@ -485,25 +666,24 @@ class Controller::query_state private: struct SourceLevel { - explicit SourceLevel(size_t n) - : outputs_available(false) - , outputs(n) + explicit SourceLevel(size_t number_of_outputs) + : outputs(number_of_outputs) {} typename Renderer::sample_type source_level; - int source_id; + std::string source_id; - bool outputs_available; - // this may never be resized: + bool outputs_available{false}; + // this must never be resized: std::vector outputs; }; using source_levels_t = std::vector; Controller& _controller; - Renderer& _renderer; - std::pair _state; + const Renderer& _renderer; + std::pair _state; float _cpu_load; typename Renderer::sample_type _master_level; @@ -531,15 +711,18 @@ bool Controller::run() _renderer.new_query(_query_state); + { + auto control = this->take_control(); + control->processing(true); + if (!_conf.follow) + { + control->transport_locate(0.0f); + } + } + if (_conf.gui) { #ifdef ENABLE_GUI - - // TEMPORARY!!! - this->start_processing(); - this->transport_locate(0.0f); - //this->transport_start(); - if (!_start_gui(_conf.path_to_gui_images, _conf.path_to_scene_menu)) { return false; @@ -551,13 +734,7 @@ bool Controller::run() } else // without GUI { - // TODO: check if IP-server is running - // TODO: wait for shutdown command (run forever) - - // TEMPORARY!!! - this->start_processing(); - this->transport_locate(0.0f); - this->transport_start(); + this->take_control()->transport_start(); bool keep_running = true; while (keep_running) { @@ -565,19 +742,19 @@ bool Controller::run() { case 'c': std::cout << "Calibrating client.\n"; - this->calibrate_client(); + this->take_control()->calibrate_tracker(); break; case 'p': - this->transport_start(); + this->take_control()->transport_start(); break; case 'q': keep_running = false; break; case 'r': - this->transport_locate(0.0f); + this->take_control()->transport_locate(0.0f); break; case 's': - this->transport_stop(); + this->take_control()->transport_stop(); break; } } @@ -588,23 +765,19 @@ bool Controller::run() template Controller::~Controller() { - //recorder->disable(); - this->stop_processing(); - - // TODO: check if transport needs to be stopped - this->transport_stop(); - - if (!this->save_scene_as_XML("ssr_scene_autosave.asd")) + if (!_conf.follow) { - ERROR("Couldn't write XML scene! (It's an ugly hack anyway ..."); - } + auto control = this->take_control(); - { - ScopedLock guard(_subscribers_lock); - _subscribers.clear(); - } // unlock + // TODO: check if transport needs to be stopped + control->transport_stop(); - this->deactivate(); + // NB: Scene is save while holding the lock + if (!_save_scene("ssr_scene_autosave.asd")) + { + ERROR("Couldn't write XML scene! (It's an ugly hack anyway ..."); + } + } } namespace internal @@ -707,19 +880,28 @@ get_orientation(const Node& node) **/ template T -get_attribute_of_node(const Node& node, const std::string attribute +get_attribute_of_node(const Node& node, const std::string& attribute , const T default_value) { if (!node) return default_value; return apf::str::S2RV(node.get_attribute(attribute), default_value); } +/// Overload for const char* default value +inline std::string +get_attribute_of_node(const Node& node, const std::string& attribute + , const char* default_value) +{ + std::string temp{default_value}; + return get_attribute_of_node(node, attribute, temp); +} + /** check for file/port * @param node parent node * @param file_name_or_port_number a string where the obtained file name or * port number is stored. * @return channel number, 0 if port was given. - * @note on error, @p file_name_or_port_number is set to the empty string "" + * @note on error, @a file_name_or_port_number is set to the empty string "" * and 0 is returned. **/ inline int @@ -732,7 +914,6 @@ get_file_name_or_port_number(const Node& node { file_name_or_port_number = get_content(i); int channel = apf::str::S2RV(i.get_attribute("channel"), 1); - // TODO: raise error if channel is negative? assert(channel >= 0); return channel; } @@ -747,41 +928,458 @@ get_file_name_or_port_number(const Node& node return 0; } -} // end of anonymous namespace +} // end of namespace "internal" + + +template +template +class Controller::CommonInterface + : virtual public api::SceneControlEvents +{ +public: + explicit CommonInterface(X* initiator, Controller& controller) + : _initiator(initiator) + , _controller(controller) + , _lock(_controller._m) + { + _controller._publish(&api::BundleEvents::bundle_start); + } + + ~CommonInterface() + { + _controller._publish(&api::BundleEvents::bundle_stop); + } + + // SceneControlEvents + + void auto_rotate_sources(bool auto_rotate) override + { + _controller._publish(_initiator, + &api::SceneControlEvents::auto_rotate_sources, auto_rotate); + + if constexpr (_is_leader) + { + if (auto_rotate) + { + _controller._orient_all_sources_toward_reference(); + VERBOSE("Auto-rotation of sound sources is enabled."); + } + else + { + VERBOSE("Auto-rotation of sound sources is disabled."); + } + } + } + + void delete_source(id_t id) override + { + _controller._publish(_initiator, + &api::SceneControlEvents::delete_source, id); + + // TODO: stop AudioPlayer if not needed anymore? + } + + void source_position(id_t id, const Pos& position) override + { + if constexpr (_is_leader) + { + auto* src = _controller._scene.get_source(id); + if (src == nullptr) + { + WARNING("Source \"" << id << "\" does not exist."); + return; + } + else if (src->fixed) + { + WARNING("Source \"" << id << "\" cannot be moved because it is fixed."); + return; + } + } + _controller._publish(_initiator, + &api::SceneControlEvents::source_position, id, position); + if constexpr (_is_leader) + { + if (_controller._scene.get_auto_rotation()) + { + _controller._orient_source_toward_reference(id); + } + } + } + + void source_rotation(id_t id, const Rot& rotation) override + { + if constexpr (_is_leader) + { + if (_controller._scene.get_auto_rotation()) + { + VERBOSE2("Ignoring update of source rotation." + << " Auto-rotation is enabled."); + return; + } + auto* src = _controller._scene.get_source(id); + if (src == nullptr) + { + WARNING("Source \"" << id << "\" does not exist."); + return; + } + if (src->fixed) + { + WARNING("Source \"" << id + << "\" cannot be rotated because it is fixed."); + return; + } + } + _controller._publish(_initiator, + &api::SceneControlEvents::source_rotation, id, rotation); + } + + void source_volume(id_t id, float volume) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::source_volume, id, volume); + } + + void source_mute(id_t id, bool mute) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::source_mute, id, mute); + } + + void source_name(id_t id, const std::string& name) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::source_name, id, name); + } + + void source_model(id_t id, const std::string& model) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::source_model, id, model); + } + + void source_fixed(id_t id, bool fixed) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::source_fixed, id, fixed); + } + + void reference_position(const Pos& position) override + { + _controller._publish(_initiator, + &api::SceneControlEvents::reference_position, position); + if constexpr (_is_leader) + { + if (_controller._scene.get_auto_rotation()) + { + _controller._orient_all_sources_toward_reference(); + } + } + } + + void reference_rotation(const Rot& rotation) override + { + _controller._publish(_initiator, + &api::SceneControlEvents::reference_rotation, rotation); + } + + void master_volume(float volume) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::master_volume, volume); + } + + void decay_exponent(float exponent) override + { + _controller._publish(_initiator + , &api::SceneControlEvents::decay_exponent, exponent); + } + + void amplitude_reference_distance(float dist) override + { + if (dist > 1.0f) + { + _controller._publish(_initiator, + &api::SceneControlEvents::amplitude_reference_distance, dist); + } + else + { + ERROR("Amplitude reference distance cannot be smaller than 1."); + } + } + +protected: + static constexpr bool _is_leader = !std::is_same_v; + X* _initiator; + Controller& _controller; + std::lock_guard _lock; +}; + + +template +template +class Controller::ControlInterface : public CommonInterface + , public api::Controller +{ +public: + using CommonInterface::CommonInterface; + + void load_scene(const std::string& filename) override + { + if constexpr (_is_leader) + { + if (!_controller._load_scene(filename)) + { + WARNING("Loading scene \"" << filename << "\" failed"); + } + } + else + { + _controller._call_leader(&api::Controller::load_scene, filename); + } + } + + void save_scene(const std::string& filename) const override + { + if constexpr (_is_leader) + { + if (!_controller._save_scene(filename)) + { + WARNING("Saving scene \"" << filename << "\" failed"); + } + } + else + { + _controller._call_leader(&api::Controller::save_scene, filename); + } + } + + void new_source(const std::string& id, const std::string& name + , const std::string& model, const std::string& file_name_or_port_number + , int channel, const Pos& position, const Rot& rotation, bool fixed + , float volume, bool mute, const std::string& properties_file) override + { + if constexpr (_is_leader) + { + _controller._new_source(id, name, model, file_name_or_port_number + , channel, position, rotation, fixed, volume, mute, properties_file); + } + else + { + _controller._call_leader(&api::Controller::new_source, id, name, model + , file_name_or_port_number, channel, position, rotation, fixed + , volume, mute, properties_file); + } + } + + void delete_all_sources() override + { + if constexpr (_is_leader) + { + _controller._delete_all_sources(); + } + else + { + _controller._call_leader(&api::Controller::delete_all_sources); + } + } + + void transport_start() override + { + if constexpr (_is_leader) + { + _controller._renderer.transport_start(); + } + else + { + _controller._call_leader(&api::Controller::transport_start); + } + } + + void transport_stop() override + { + if constexpr (_is_leader) + { + _controller._renderer.transport_stop(); + } + else + { + _controller._call_leader(&api::Controller::transport_stop); + } + } + + void transport_locate(float time) override + { + if constexpr (_is_leader) + { + _controller._transport_locate(time); + } + else + { + _controller._call_leader(&api::Controller::transport_locate, time); + } + } + + void calibrate_tracker() override + { + // NB: This is not sent to the leader + _controller._calibrate_client(); + } + + std::string get_source_id(unsigned source_number) const override + { + return _controller._scene.get_source_id(source_number); + } + + // RendererControlEvents + + void processing(bool state) override + { + _controller._publish(&api::RendererControlEvents::processing, state); + } + + void reference_offset_position(const Pos& position) override + { + _controller._publish(&api::RendererControlEvents::reference_offset_position + , position); + } + + void reference_offset_rotation(const Rot& rotation) override + { + _controller._publish(&api::RendererControlEvents::reference_offset_rotation + , rotation); + } + +private: + using CommonInterface::_is_leader; + using CommonInterface::_controller; +}; + + +template +class Controller::FollowerInterface : public CommonInterface<> + , public api::Follower +{ +private: + using CommonInterface<>::_controller; + +public: + explicit FollowerInterface(Controller& controller) + : CommonInterface<>(nullptr, controller) + {} + + // SceneControlEvents are inherited from CommonInterface + + // SceneInformationEvents + + void new_source(id_t id) override + { + _controller._publish(&api::SceneInformationEvents::new_source, id); + } + + void source_property(id_t id, const std::string& key + , const std::string& value) override + { + _controller._publish(&api::SceneInformationEvents::source_property + , id, key, value); + } + + // TransportEvents + + void transport_state(bool rolling, uint32_t frame) override + { + _controller._publish(&api::TransportEvents::transport_state + , rolling, frame); + } + + // SourceMetering + + void source_level(id_t id, float level) override + { + _controller._publish(&api::SourceMetering::source_level, id, level); + } +}; + + +template +std::unique_ptr +Controller::subscribe_leader(api::Controller* leader) +{ + if (!_conf.follow) + { + throw std::logic_error( + "\"subscribe_leader()\" can only be called on \"follower\""); + } + std::lock_guard lock{_m}; + if (_leader != nullptr) + { + throw std::runtime_error("A \"leader\" is already subscribed"); + } + _leader = leader; + return std::make_unique([this]() { + std::lock_guard lock{_m}; + _leader = nullptr; + }); +} + + +template +std::unique_ptr +Controller::take_control(api::SceneControlEvents* suppress_own) +{ + if (_conf.follow) + { + if (suppress_own != nullptr) + { + throw std::logic_error( + "\"suppress_own\" can only be used directly on \"leader\""); + } + return std::make_unique>( + nullptr, *this); + } + else + { + return std::make_unique>( + suppress_own, *this); + } +} + + +template +std::unique_ptr +Controller::take_control() +{ + return this->take_control(nullptr); +} + template -void -Controller::subscribe(Subscriber* const subscriber) +std::unique_ptr +Controller::update_follower() { - ScopedLock guard(_subscribers_lock); - _subscribers.push_back(subscriber); + return std::make_unique(*this); } + template -void -Controller::unsubscribe(Subscriber* subscriber) +unsigned int +Controller::get_source_number(id_t source_id) const { - ScopedLock guard(_subscribers_lock); - auto s = std::find(_subscribers.begin(), _subscribers.end(), subscriber); - if (s != _subscribers.end()) - { - _subscribers.erase(s); - } - else - { - WARNING("unsubscribe(): given subscriber not found!"); - } + return _scene.get_source_number(source_id); } + template bool -Controller::load_scene(const std::string& scene_file_name) +Controller::_load_scene(const std::string& scene_file_name) { - this->stop_processing(); - // TODO: get state. + assert(!_conf.follow); // remove all existing sources (if any) - this->delete_all_sources(); + _delete_all_sources(); + +#ifdef ENABLE_ECASOUND + _audio_player.reset(); // shut down audio player +#endif if (scene_file_name == "") { @@ -838,7 +1436,8 @@ Controller::load_scene(const std::string& scene_file_name) } VERBOSE("Setting master volume to " << master_volume << " dB."); - this->set_master_volume(apf::math::dB2linear(master_volume)); + _publish(&api::SceneControlEvents::master_volume + , apf::math::dB2linear(master_volume)); // GET DECAY EXPONENT auto exponent = _conf.renderer_params.get("decay_exponent"); @@ -854,7 +1453,7 @@ Controller::load_scene(const std::string& scene_file_name) // always use default value when nothing is specified VERBOSE("Setting amplitude decay exponent to " << exponent << "."); - this->set_decay_exponent(exponent); + _publish(&api::SceneControlEvents::decay_exponent, exponent); // GET AMPLITUDE REFERENCE DISTANCE @@ -874,7 +1473,7 @@ Controller::load_scene(const std::string& scene_file_name) // always use default value when nothing is specified VERBOSE("Setting amplitude reference distance to " << ref_dist << " meters."); - this->set_amplitude_reference_distance(ref_dist); + _publish(&api::SceneControlEvents::amplitude_reference_distance, ref_dist); // LOAD REFERENCE @@ -901,10 +1500,8 @@ Controller::load_scene(const std::string& scene_file_name) if (!pos_ptr) pos_ptr.reset(new internal::PositionPlusBool()); if (!dir_ptr) dir_ptr.reset(new Orientation(90)); - this->set_reference_position(*pos_ptr); - this->set_reference_orientation(*dir_ptr); - this->set_reference_offset_position(Position()); - this->set_reference_offset_orientation(Orientation()); + _publish(&api::SceneControlEvents::reference_position, *pos_ptr); + _publish(&api::SceneControlEvents::reference_rotation, *dir_ptr); // LOAD SOURCES @@ -929,17 +1526,16 @@ Controller::load_scene(const std::string& scene_file_name) std::string name_str; if (name != "") name_str = " name: \"" + name + "\""; - Source::model_t model - = internal::get_attribute_of_node(node, "model", Source::unknown); + std::string model = internal::get_attribute_of_node(node, "model", ""); - if (model == Source::unknown) + if (model == "") { VERBOSE("Source model not defined!" << id_str << name_str << " Using default (= point source)."); - model = Source::point; + model = "point"; } - if ((model == Source::point) && !dir_ptr) + if ((model == "point") && !dir_ptr) { // orientation is optional for point sources, required for plane waves dir_ptr.reset(new Orientation); @@ -966,10 +1562,11 @@ Controller::load_scene(const std::string& scene_file_name) float gain_dB = internal::get_attribute_of_node(node, "volume", 0.0f); bool muted = internal::get_attribute_of_node(node, "mute", false); pos_ptr->fixed = internal::get_attribute_of_node(node, "fixed", false); - // bool doppler = internal::get_attribute_of_node(node, "doppler_effect", false); - this->new_source(name, model, file_name_or_port_number, channel - , *pos_ptr, pos_ptr->fixed, *dir_ptr, false + // NB: If ID is not the empty string and not unique, this will fail: + _new_source(id, name, model, file_name_or_port_number + , channel, Pos{pos_ptr->x, pos_ptr->y} + , *dir_ptr, pos_ptr->fixed , apf::math::dB2linear(gain_dB), muted, properties_file); } } @@ -987,18 +1584,17 @@ Controller::load_scene(const std::string& scene_file_name) return false; } } - - this->transport_locate(0); // go to beginning of audio files - // TODO: only start processing if it was on before - this->start_processing(); - + _transport_locate(0); // go to beginning of audio files return true; } template bool -Controller::_create_spontaneous_scene(const std::string& audio_file_name) +Controller::_create_spontaneous_scene( + const std::string& audio_file_name) { + assert(!_conf.follow); + #ifndef ENABLE_ECASOUND ERROR("Couldn't create scene from file \"" << audio_file_name << "\"! Ecasound was disabled at compile time."); @@ -1027,58 +1623,58 @@ Controller::_create_spontaneous_scene(const std::string& audio_file_na = audio_file_name.substr(audio_file_name.rfind('/') + 1); // set master volume - this->set_master_volume(apf::math::dB2linear(0.0f)); - this->set_decay_exponent(_conf.renderer_params.get("decay_exponent")); - this->set_amplitude_reference_distance(_conf.renderer_params.get( - "amplitude_reference_distance")); // throws on error! + _publish(&api::SceneControlEvents::master_volume, apf::math::dB2linear(0.0f)); + _publish(&api::SceneControlEvents::decay_exponent, + // throws on error! + _conf.renderer_params.get("decay_exponent")); + _publish(&api::SceneControlEvents::amplitude_reference_distance, + // throws on error! + _conf.renderer_params.get("amplitude_reference_distance")); // set reference - this->set_reference_position(Position()); - this->set_reference_orientation(Orientation(90.0f)); - this->set_reference_offset_position(Position()); - this->set_reference_offset_orientation(Orientation()); + _publish(&api::SceneControlEvents::reference_position, Pos{}); + _publish(&api::SceneControlEvents::reference_rotation, Rot{}); const float default_source_distance = 2.5f; // for mono and stereo files switch (no_of_audio_channels) { case 1: // mono file - this->new_source(source_name, Source::point, audio_file_name, 1 - , Position(0.0f, default_source_distance), false, Orientation() - , false, apf::math::dB2linear(0.0f), false, ""); - - VERBOSE("Creating point source at x = " - << apf::str::A2S(0.0f) << " mtrs, y = " - << apf::str::A2S(default_source_distance) << " mtrs."); - + { + Pos pos{0, default_source_distance}; + VERBOSE("Creating point source at x = " << pos.x + << " mtrs, y = " << pos.y << " mtrs."); + assert(pos.z == 0); + _new_source("", source_name, "point", audio_file_name, 1 + , pos, Rot{}, false, 1.0f, false, ""); + } break; case 2: // stereo file { -#undef PI - const float PI = 3.14159265358979323846; - - const float pos_x = default_source_distance * cos(PI/3.0f); - const float pos_y = default_source_distance * sin(PI/3.0f); + constexpr auto pi = apf::math::pi(); - // create source - this->new_source(source_name + " left", Source::plane, audio_file_name - , 1, Position(-pos_x, pos_y), false, Orientation(-60), false - , apf::math::dB2linear(0.0f), false, ""); + const float pos_x = default_source_distance * std::cos(pi/3.0f); + const float pos_y = default_source_distance * std::sin(pi/3.0f); - VERBOSE("Creating point source at x = " << apf::str::A2S(-pos_x) - << " mtrs, y = " << apf::str::A2S(pos_y) << " mtrs."); + Pos pos{-pos_x, pos_y}; + VERBOSE("Creating plane wave at x = " << pos.x + << " mtrs, y = " << pos.y << " mtrs."); + assert(pos.z == 0); - // create source - this->new_source(source_name + " right", Source::plane, audio_file_name - , 2u, Position(pos_x, pos_y), false, Orientation(-120), false - , apf::math::dB2linear(0.0f), false, ""); + _new_source("", source_name + " left", "plane" + , audio_file_name, 1, pos, Orientation(-60) + , false, 1.0f, false, ""); - VERBOSE("Creating point source at x = " - << apf::str::A2S(pos_x) << " mtrs, y = " - << apf::str::A2S(pos_y) << " mtrs."); + pos = Pos{pos_x, pos_y}; + VERBOSE("Creating plane wave at x = " << pos.x + << " mtrs, y = " << pos.y << " mtrs."); + assert(pos.z == 0); - break; + _new_source("", source_name + " right", "plane" + , audio_file_name, 2, pos, Orientation(-120) + , false, 1.0f, false, ""); } + break; default: // init random position generator @@ -1096,15 +1692,16 @@ Controller::_create_spontaneous_scene(const std::string& audio_file_na const float pos_y = 2.0f + (static_cast(rand()%(10*(N+1))) - (5.0f*(N+1))) / 10.0f; - VERBOSE("Creating point source at x = " - << apf::str::A2S(pos_x) << " mtrs, y = " - << apf::str::A2S(pos_y) << " mtrs."); + Pos pos{pos_x, pos_y}; + + VERBOSE("Creating point source at x = " << pos.x + << " mtrs, y = " << pos.y << " mtrs."); + assert(pos.z == 0); - // create sources - this->new_source(source_name + " " + apf::str::A2S(n+1), Source::point - , audio_file_name, n+1u, Position(pos_x, pos_y), false - , Orientation(), false, apf::math::dB2linear(0.0f), false, ""); - } // for each audio channel + _new_source("", source_name + " " + apf::str::A2S(n + 1) + , "point", audio_file_name, n + 1, pos, Rot{} + , false, 1.0f, false, ""); + } } // switch return true; #endif // ENABLE_ECASOUND @@ -1126,7 +1723,7 @@ Controller::_start_gui(const std::string& path_to_gui_images gl_format.setDepth(true); QGLFormat::setDefaultFormat(gl_format); - _gui.reset(new QGUI(*this, _scene, _argc, _argv, + _gui.reset(new QGUI(*this, _legacy_scene, _argc, _argv, path_to_gui_images, path_to_scene_menu)); // check if anti-aliasing is possible @@ -1207,7 +1804,7 @@ Controller::_start_tracker(const std::string& type, const std::string& /// This is temporary!!!! template void -Controller::calibrate_client() +Controller::_calibrate_client() { #if defined(ENABLE_INTERSENSE) || defined(ENABLE_POLHEMUS) || defined(ENABLE_VRPN) || defined(ENABLE_RAZOR) if (_tracker) @@ -1221,6 +1818,15 @@ Controller::calibrate_client() #endif } + +template +void +Controller::_transport_locate(float time) +{ + _renderer.transport_locate( + static_cast(time * _renderer.sample_rate())); +} + #ifdef ENABLE_ECASOUND /** * The recorder is started as soon the JACK transport is running. @@ -1258,123 +1864,24 @@ Controller::_load_audio_recorder(const std::string& audio_file_name } #endif -/** start audio processing. - * This sets the Scene's processing state to "processing". The - * process callback function has to check for this variable and act accordingly. - **/ -template -void -Controller::start_processing() -{ - if (!_scene.get_processing_state()) - { - this->set_processing_state(true); - } - else - { - WARNING("Renderer is already processing."); - } -} - -/** Stop audio processing. - * This sets the Scene's processing state to "ready". The - * process callback function has to check for this variable and act - * accordingly. - **/ -template -void -Controller::stop_processing() -{ - if (_scene.get_processing_state()) - { - this->set_processing_state(false); - } - else - { - WARNING("Renderer was already stopped."); - } -} - -/** _. - * @param state processing state. - * @warning States are not checked for validity. To start and stop rendering, - * use preferably start_processing() and stop_processing(). This function should - * only be used by the respective renderer to set the state "ready" and by - * anyone who wants to set the state "exiting". - **/ -template -void -Controller::set_processing_state(bool state) -{ - _publish(&Subscriber::set_processing_state, state); -} - -template -void -Controller::set_auto_rotation(bool auto_rotate_sources) -{ - _publish(&Subscriber::set_auto_rotation, auto_rotate_sources); - - if (auto_rotate_sources) - { - orient_all_sources_toward_reference(); - - VERBOSE("Auto-rotation of sound sources is enabled."); - } - else VERBOSE("Auto-rotation of sound sources is disabled."); - -} - -// non-const because audioplayer could be started -template -void -Controller::transport_start() -{ - _renderer.transport_start(); -} - -// non-const because audioplayer could be started -template -void -Controller::transport_stop() -{ - _renderer.transport_stop(); -} - -/** Skips the scene to a specified instant of time - * @ param frame instant of time in sec to locate - **/ -template -bool -Controller::transport_locate(float time) -{ - // convert time to samples (cut decimal part) - return _renderer.transport_locate( - static_cast(time * _renderer.sample_rate())); -} - /** Create a new source. - * @param name Source name * @param model Source model * @param file_name_or_port_number File name or port number (as string) * @param channel Channel of soundfile. If 0, a JACK portname is expected. - * @param position initial position of the source. - * @param orientation initial orientation of the source. - * @param gain gain (=volume) of the source. - * @return ID of the created source. If 0, no source was created. **/ template void -Controller::new_source(const std::string& name - , const Source::model_t model - , const std::string& file_name_or_port_number, int channel - , const Position& position, const bool pos_fixed - , const Orientation& orientation, const bool or_fixed, const float gain - , const bool muted, const std::string& properties_file) +Controller::_new_source(id_t requested_id, const std::string& name + , const std::string& model, const std::string& file_name_or_port_number + , int channel, const Pos& position, const Rot& rotation, bool fixed + , float volume, bool mute, const std::string& properties_file) { - (void) or_fixed; - assert(channel >= 0); + // TODO: similar function for follower? just using a JACK port, no audio file + + assert(!_conf.follow); + + std::string id = requested_id; std::string port_name; long int file_length = 0; @@ -1399,6 +1906,8 @@ Controller::new_source(const std::string& name } else // no audio file { + assert(channel == 0); + if (file_name_or_port_number != "") { port_name = _conf.input_port_prefix + file_name_or_port_number; @@ -1407,338 +1916,96 @@ Controller::new_source(const std::string& name if (port_name == "") { - VERBOSE("No audio file or port specified for source '" << name << "'."); + VERBOSE("No audio file or port specified for source"); } apf::parameter_map p; p.set("connect_to", port_name); p.set("properties_file", properties_file); - id_t id; - try { - auto guard = _renderer.get_scoped_lock(); - id = _renderer.add_source(p); + id = _renderer.add_source(id, p); } catch (std::exception& e) { ERROR(e.what()); return; } + assert(requested_id.size() == 0 || requested_id == id); - _publish(&Subscriber::new_source, id); - // mute while transmitting data - _publish(&Subscriber::set_source_mute, id, true); - _publish(&Subscriber::set_source_gain, id, gain); - - // make sure that source orientation is handled correctly - this->set_source_position(id, position); + _publish(&api::SceneInformationEvents::new_source, id); + _publish(&api::SceneInformationEvents::source_property + , id, "port_name", port_name); - _publish(&Subscriber::set_source_position_fixed, id, pos_fixed); - - // make sure that source orientation is handled correctly - this->set_source_orientation(id, orientation); - - // _publish(&Subscriber::set_source_orientation_fix, id, or_fix); - _publish(&Subscriber::set_source_name, id, name); - _publish(&Subscriber::set_source_model, id, model); - _publish(&Subscriber::set_source_port_name, id, port_name); if (file_name_or_port_number != "") { - _publish(&Subscriber::set_source_file_name, id, file_name_or_port_number); - _publish(&Subscriber::set_source_file_channel, id, channel); + _publish(&api::SceneInformationEvents::source_property + , id, "audio_file", file_name_or_port_number); + _publish(&api::SceneInformationEvents::source_property + , id, "audio_file_channel", apf::str::A2S(channel)); + _publish(&api::SceneInformationEvents::source_property + , id, "audio_file_length", apf::str::A2S(file_length)); } - _publish(&Subscriber::set_source_file_length, id, file_length); - _publish(&Subscriber::set_source_properties_file, id, properties_file); - // finally, unmute if requested - _publish(&Subscriber::set_source_mute, id, muted); -} - -template -void -Controller::delete_all_sources() -{ - _publish(&Subscriber::delete_all_sources); -#ifdef ENABLE_ECASOUND - _audio_player.reset(); // shut down audio player -#endif - // Wait until InternalInput objects are destroyed - _renderer.wait_for_rt_thread(); -} - -template -void -Controller::delete_source(id_t id) -{ - _publish(&Subscriber::delete_source, id); - // TODO: stop AudioPlayer if not needed anymore? + _publish(&api::SceneInformationEvents::source_property + , id, "properties_file", properties_file); + + _publish(&api::SceneControlEvents::source_name, id, name); + _publish(&api::SceneControlEvents::source_model, id, model); + _publish(&api::SceneControlEvents::source_position, id, position); + _publish(&api::SceneControlEvents::source_rotation, id, rotation); + _publish(&api::SceneControlEvents::source_fixed, id, fixed); + _publish(&api::SceneControlEvents::source_volume, id, volume); + _publish(&api::SceneControlEvents::source_mute, id, mute); } template void -Controller::set_source_position(const id_t id, const Position& position) +Controller::_delete_all_sources() { - // TODO: check if the client who sent the request is actually allowed to - // change the position. (same TODO as above) - - // TODO: check if position is inside of room boundaries. - // if not: change position (e.g. single dimensions) to an allowed position - - // check if source may be moved - if (!_scene.get_source_position_fixed(id)) - { - _publish(&Subscriber::set_source_position, id, position); + assert(!_conf.follow); - // make source face the reference - if (_scene.get_auto_rotation()) - { - // new orientation will be published automatically - orient_source_toward_reference(id); - } - } - else + std::string id; + while (!(id = _scene.get_source_id(1)).empty()) { - WARNING("Source \'" << _scene.get_source_name(id) << "\' cannot be moved."); + _publish(&api::SceneControlEvents::delete_source, id); + // NB: This assumes that _scene is subscribed! } } template void -Controller::set_source_orientation(const id_t id - , const Orientation& orientation) +Controller::_orient_source_toward_reference(id_t id) { - // TODO: validate orientation? + assert(!_conf.follow); - // check if source may be rotated - if (!_scene.get_source_position_fixed(id)) + auto* src = _scene.get_source(id); + if (src) { - if (_scene.get_auto_rotation()) - { - VERBOSE2("Ignoring update of source orientation." - << " Auto-rotation is enabled."); - } - else - { - _publish(&Subscriber::set_source_orientation, id, orientation); - } + // NB: Reference offset is not taken into account + // (because it is a property of the renderer, not the scene) + auto ref_pos = _scene.get_reference_position(); + _publish(&api::SceneControlEvents::source_rotation + , id, look_at(src->position, ref_pos)); } else { - WARNING("Source \'" << _scene.get_source_name(id) - << "\' cannot be rotated."); - } -} - -template -void -Controller::orient_source_toward_reference(const id_t id) -{ - // take reference offset into account? - if (auto src_pos = _scene.get_source_position(id)) - { - _publish(&Subscriber::set_source_orientation, id - , (_scene.get_reference().position - *src_pos).orientation()); + WARNING("Auto-rotation: Source \"" << id << "\" doesn't exist"); } } template void -Controller::orient_all_sources_toward_reference() +Controller::_orient_all_sources_toward_reference() { - typename SourceCopy::container_t sources; - - _scene.get_sources(sources); - - for (const auto& source: sources) - { - // check if sources may be rotated - if (!_scene.get_source_position_fixed(source.id)) + _scene.for_each_source([this](auto id, const auto& source){ + if (!source.fixed) { - // new orientation will be published automatically - orient_source_toward_reference(source.id); + _orient_source_toward_reference(id); } - } -} - -template -void -Controller::set_source_gain(const id_t id, const float gain) -{ - _publish(&Subscriber::set_source_gain, id, gain); -} - -template -void -Controller::set_source_signal_level(const id_t id, const float level) -{ - _publish(&Subscriber::set_source_signal_level, id, level); -} - -template -void -Controller::set_source_mute(const id_t id, const bool mute) -{ - _publish(&Subscriber::set_source_mute, id, mute); -} - -template -void -Controller::set_source_name(const id_t id, const std::string& name) -{ - _publish(&Subscriber::set_source_name, id, name); -} - -template -void -Controller::set_source_properties_file(const id_t id, const std::string& name) -{ - _publish(&Subscriber::set_source_properties_file, id, name); -} - -template -void -Controller::set_source_model(const id_t id, const Source::model_t model) -{ - _publish(&Subscriber::set_source_model, id, model); -} - -template -void -Controller::set_source_port_name(const id_t id, const std::string& port_name) -{ - _publish(&Subscriber::set_source_port_name, id, port_name); -} - -template -void -Controller::set_source_file_name(const id_t id, const std::string& file_name) -{ - _publish(&Subscriber::set_source_file_name, id, file_name); -} - -template -void -Controller::set_source_file_channel(const id_t id, const int& channel) -{ - _publish(&Subscriber::set_source_file_channel, id, channel); -} - -template -void -Controller::set_source_position_fixed(const id_t id, const bool fixed) -{ - _publish(&Subscriber::set_source_position_fixed, id, fixed); -} - -template -void -Controller::set_reference_position(const Position& position) -{ - _publish(&Subscriber::set_reference_position, position); - - // make sources face the reference - if (_scene.get_auto_rotation()) orient_all_sources_toward_reference(); -} - -template -void -Controller::set_reference_orientation(const Orientation& orientation) -{ - _publish(&Subscriber::set_reference_orientation, orientation); -} - -template -void -Controller::set_reference_offset_position(const Position& position) -{ - _publish(&Subscriber::set_reference_offset_position, position); - - // make sources face the reference // has no effect currently - //if (_scene.get_auto_rotation()) orient_all_sources_toward_reference(); -} - -template -void -Controller::set_reference_offset_orientation(const Orientation& orientation) -{ - _publish(&Subscriber::set_reference_offset_orientation, orientation); -} - -// linear volume! -template -void -Controller::set_master_volume(const float volume) -{ - // TODO: validate volume? - _publish(&Subscriber::set_master_volume, volume); -} - -template -void -Controller::set_decay_exponent(const float exponent) -{ - // TODO: validate exponent? - _publish(&Subscriber::set_decay_exponent, exponent); -} - -template -void -Controller::set_amplitude_reference_distance(const float dist) -{ - if (dist > 1.0f) - { - _publish(&Subscriber::set_amplitude_reference_distance, dist); - } - else - { - ERROR("Amplitude reference distance cannot be smaller than 1."); - } -} - -// linear scale -template -void -Controller::set_master_signal_level(float level) -{ - _publish(&Subscriber::set_master_signal_level, level); -} - -template -void -Controller::set_cpu_load(const float load) -{ - _publish(&Subscriber::set_cpu_load, load); -} - -template -void -Controller::publish_sample_rate(const int sample_rate) -{ - _publish(&Subscriber::set_sample_rate, sample_rate); -} - -template -std::string -Controller::get_renderer_name() const -{ - return _renderer.params.get("name", ""); -} - -template -bool -Controller::show_head() const -{ - return _renderer.show_head(); -} - -template -void -Controller::set_source_output_levels(id_t id, float* first - , float* last) -{ - _publish(&Subscriber::set_source_output_levels, id, first, last); + }); } +#ifdef ENABLE_IP_INTERFACE template std::string Controller::get_scene_as_XML() const @@ -1759,10 +2026,11 @@ Controller::get_scene_as_XML() const return node.to_string(); } +#endif template bool -Controller::save_scene_as_XML(const std::string& filename) const +Controller::_save_scene(const std::string& filename) const { // ATTENTION: this is an ugly work-around/quick-hack! // TODO: the following should be included into the XMLParser wrapper! @@ -1808,7 +2076,7 @@ template void Controller::_add_master_volume(Node& node) const { - float volume = _scene.get_master_volume(); + float volume = _legacy_scene.get_master_volume(); node.new_child("volume", apf::str::A2S(apf::math::linear2dB(volume))); } @@ -1824,7 +2092,7 @@ template void Controller::_add_reference(Node& node) const { - DirectionalPoint reference = _scene.get_reference(); + DirectionalPoint reference = _legacy_scene.get_reference(); Node reference_node = node.new_child("reference"); _add_position(reference_node, reference.position); _add_orientation(reference_node, reference.orientation); @@ -1856,8 +2124,8 @@ template void Controller::_add_loudspeakers(Node& node) const { - Loudspeaker::container_t loudspeakers; - _scene.get_loudspeakers(loudspeakers, false); // get relative positions + LegacyLoudspeaker::container_t loudspeakers; + _legacy_scene.get_loudspeakers(loudspeakers, false); // get relative positions for (const auto& ls: loudspeakers) { Node loudspeaker_node = node.new_child("loudspeaker"); @@ -1872,10 +2140,8 @@ void Controller::_add_sources(Node& node , const std::string& scene_file_name) const { - typename SourceCopy::container_t sources; - _scene.get_sources(sources); - for (const auto& source: sources) - { + _legacy_scene.for_each_source([&]( + unsigned int id, const LegacySource& source) { Node source_node = node.new_child("source"); if (scene_file_name != "") { @@ -1883,7 +2149,7 @@ Controller::_add_sources(Node& node } else { - source_node.new_attribute("id", apf::str::A2S(source.id)); + source_node.new_attribute("id", apf::str::A2S(id)); } source_node.new_attribute("name", apf::str::A2S(source.name)); source_node.new_attribute("model", apf::str::A2S(source.model)); @@ -1923,7 +2189,6 @@ Controller::_add_sources(Node& node source_node.new_attribute("mute", apf::str::A2S(source.mute)); // save volume in dB! source_node.new_attribute("volume", apf::str::A2S(apf::math::linear2dB(source.gain))); - // TODO: information about mirror sources if (source.properties_file != "") { @@ -1931,9 +2196,7 @@ Controller::_add_sources(Node& node , posixpathtools::make_path_relative_to_file(source.properties_file , scene_file_name)); } - - // TODO: save doppler effect setting (source.doppler_effect) - } + }); } template diff --git a/src/dcarenderer.h b/src/dcarenderer.h index 26603c90..5a492869 100644 --- a/src/dcarenderer.h +++ b/src/dcarenderer.h @@ -114,21 +114,24 @@ class DcaRenderer::Source : public _base::Source auto source_orientation = Orientation(); - switch (this->model) + const std::string& model = this->model; + if (model == "point") { - default: - case ::Source::point: - this->source_model = coeff_t::point_source; - source_orientation = (this->position - - this->parent.state.reference_position).orientation(); - // TODO: Undo inherent amplitude decay - break; - case ::Source::plane: - this->source_model = coeff_t::plane_wave; - source_orientation = this->orientation - Orientation(180); - // Note: no distance attenuation for plane waves! - // TODO: constant factor using amplitude_reference_distance()? - break; + this->source_model = coeff_t::point_source; + source_orientation = (this->position + - this->parent.state.reference_position).orientation(); + // TODO: Undo inherent amplitude decay + } + else if (model == "plane") + { + this->source_model = coeff_t::plane_wave; + source_orientation = this->orientation - Orientation(180); + // Note: no distance attenuation for plane waves! + // TODO: constant factor using amplitude_reference_distance()? + } + else + { + // TODO: warning } this->angle = apf::math::deg2rad(180 + (source_orientation @@ -545,7 +548,7 @@ DcaRenderer::load_reproduction_setup() auto add_distance = [] (float base, const Output& out) { - if (out.model == Loudspeaker::subwoofer) + if (out.model == LegacyLoudspeaker::subwoofer) { throw std::logic_error("Subwoofers are currently not supported!"); } diff --git a/src/geometry.h b/src/geometry.h new file mode 100644 index 00000000..0a60f391 --- /dev/null +++ b/src/geometry.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * Copyright © 2019 SSR Contributors * + * * + * This file is part of the SoundScape Renderer (SSR). * + * * + * The SSR is free software: you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * The SSR 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program. If not, see . * + * * + * The SSR is a tool for real-time spatial audio reproduction providing a * + * variety of rendering algorithms. * + * * + * http://spatialaudio.net/ssr ssr@spatialaudio.net * + ******************************************************************************/ + +/// @file +/// Data types and helper functions for positions, rotations etc. + +#ifndef SSR_GEOMETRY_H +#define SSR_GEOMETRY_H + +#include // for std::sin, std::cos, std::sqrt, ... + +#include +#include + +#include "api.h" // for Pos and Rot + +namespace ssr +{ + +/// Three-dimensional vector type. Implicitly convertible to/from Pos. +/// @see quat, https://github.com/ilmola/gml +struct vec3 : gml::vec3 +{ + using gml::vec3::vec3; + using gml::vec3::operator=; + vec3(const gml::vec3& other) : gml::vec3{other} {} + vec3(const Pos& pos) : gml::vec3{pos.x, pos.y, pos.z} {} + operator Pos() + { + return {(*this)[0], (*this)[1], (*this)[2]}; + } +}; + +/// Quaternion type for rotations. Implicitly convertible to/from Rot. +/// @see vec3, https://github.com/ilmola/gml +struct quat : gml::quat +{ + using gml::quat::quat; + using gml::quat::operator=; + quat(const gml::quat& other) : gml::quat{other} {} + quat(const Rot& rot) : gml::quat{rot.w, gml::vec3{rot.x, rot.y, rot.z}} {} + operator Rot() + { + return {this->imag[0], this->imag[1], this->imag[2], this->real}; + } +}; + +/// Build a unit quaternion representing the rotation +/// from u to v. The input vectors need not be normalised. +/// From http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final +inline quat fromtwovectors(vec3 u, vec3 v) +{ + float norm_u_norm_v = std::sqrt(gml::dot(u, u) * gml::dot(v, v)); + float real_part = norm_u_norm_v + gml::dot(u, v); + vec3 w; + + if (real_part < 1.e-6f * norm_u_norm_v) + { + /* If u and v are exactly opposite, rotate 180 degrees + * around an arbitrary orthogonal axis. Axis normalisation + * can happen later, when we normalise the quaternion. */ + real_part = 0.0f; + w = std::abs(u[0]) > std::abs(u[2]) ? vec3(-u[1], u[0], 0.f) + : vec3( 0.f, -u[2], u[1]); + } + else + { + /* Otherwise, build quaternion the standard way. */ + w = gml::cross(u, v); + } + return gml::normalize(quat(real_part, w)); +} + +inline quat look_at(vec3 from, vec3 to) +{ + return fromtwovectors({0.0f, 1.0f, 0.0f}, to - from); +} + +} // namespace ssr + +#endif diff --git a/src/gui/qgui.cpp b/src/gui/qgui.cpp index 0a52fcd8..cdc2aaa0 100644 --- a/src/gui/qgui.cpp +++ b/src/gui/qgui.cpp @@ -196,7 +196,7 @@ QString qt_style_sheet = * @param argc number of command line arguments passed to the GUI. * @param argv the arguments themselves. **/ -ssr::QGUI::QGUI(Publisher& controller, const Scene& scene, int &argc, char *argv[] +ssr::QGUI::QGUI(api::Publisher& controller, const LegacyScene& scene, int &argc, char *argv[] , const std::string& path_to_gui_images , const std::string& path_to_scene_menu) : _qt_app(argc, argv), _gui(controller, scene diff --git a/src/gui/qgui.h b/src/gui/qgui.h index f0ff7141..e4f1dccb 100644 --- a/src/gui/qgui.h +++ b/src/gui/qgui.h @@ -35,7 +35,6 @@ #include #include "quserinterface.h" -#include "publisher.h" #include "scene.h" namespace ssr @@ -50,7 +49,7 @@ class QGUI : public QObject Q_OBJECT public: - QGUI(Publisher& controller, const Scene& scene, int &argc + QGUI(api::Publisher& controller, const LegacyScene& scene, int &argc , char *argv[], const std::string& path_to_gui_images , const std::string& path_to_scene_menu); diff --git a/src/gui/qopenglplotter.cpp b/src/gui/qopenglplotter.cpp index 385bd215..74544070 100644 --- a/src/gui/qopenglplotter.cpp +++ b/src/gui/qopenglplotter.cpp @@ -47,6 +47,9 @@ #include "qopenglplotter.h" #include "qclicktextlabel.h" //#include "mathtools.h" +#include "ssr_global.h" // for ERROR() +#include "api.h" // for Publisher +#include "legacy_scene.h" // for LegacyScene #define BACKGROUNDCOLOR 0.9294f,0.9294f,0.9020f // Define how detailedly circles are plotted @@ -60,8 +63,7 @@ * @param other reference to an element of the source map **/ ssr::QOpenGLPlotter::SourceCopy::SourceCopy( - const Scene::source_map_t::value_type& other) : - //const std::pair& other) : + const std::pair& other) : DirectionalPoint(other.second), id(other.first), model(other.second.model), @@ -75,7 +77,8 @@ ssr::QOpenGLPlotter::SourceCopy::SourceCopy( //////////////////////////////////////////////////////////////////////////////// -ssr::QOpenGLPlotter::QOpenGLPlotter(Publisher& controller, const Scene& scene +ssr::QOpenGLPlotter::QOpenGLPlotter(api::Publisher& controller + , const LegacyScene& scene , const std::string& path_to_gui_images , QWidget *parent) : QGLWidget(parent), @@ -168,7 +171,7 @@ void ssr::QOpenGLPlotter::_load_background_textures() else ERROR("Texture \"" << path_to_image.toUtf8().data() << "\" not loaded."); - if (_controller.show_head()) + if (_scene.show_head()) { _plot_listener = true; @@ -499,7 +502,7 @@ void ssr::QOpenGLPlotter::_draw_objects() glLoadName(NAMESTACKOFFSET + source_buffer_list.size() * NAMESTACKSTEP + 4); - for (Loudspeaker::container_t::size_type i = 0; i < _loudspeakers.size(); ++i) + for (LegacyLoudspeaker::container_t::size_type i = 0; i < _loudspeakers.size(); ++i) { glPushMatrix(); @@ -720,7 +723,7 @@ ssr::QOpenGLPlotter::_draw_source(source_buffer_list_t::const_iterator& source, glLoadName(DUMMYINDEX); // plot orientation of plane wave - if (source->model == Source::plane) + if (source->model == LegacySource::plane) { // rotate glRotatef(static_cast(source->orientation.azimuth), 0.0f, 0.0f, 1.0f); @@ -758,7 +761,7 @@ ssr::QOpenGLPlotter::_draw_source(source_buffer_list_t::const_iterator& source, glVertex3f( 0.285f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.285f * scale, 0.005f * scale, 0.0f); glEnd(); } - else if (source->model == Source::directional) + else if (source->model == LegacySource::directional) { // rotate glRotatef((GLfloat)(source->orientation.azimuth), 0.0f, 0.0f, 1.0f); diff --git a/src/gui/qopenglplotter.h b/src/gui/qopenglplotter.h index c137e992..1476baa4 100644 --- a/src/gui/qopenglplotter.h +++ b/src/gui/qopenglplotter.h @@ -50,9 +50,8 @@ #include #include -#include "publisher.h" -#include "scene.h" #include "qsourceproperties.h" +#include "legacy_loudspeaker.h" // for LegacyLoudspeaker #define STDZOOMFACTOR 280.0f #define STDWINDOWYOFFSET -1.0f @@ -85,6 +84,9 @@ namespace ssr { +namespace api { struct Publisher; } +class LegacyScene; + /// open GL plotter class QOpenGLPlotter : public QGLWidget { @@ -93,7 +95,6 @@ class QOpenGLPlotter : public QGLWidget // TODO: Discriminate between GLfloat and float etc. protected: - struct SourceCopy; // nested class, defined later //////////////////////////////////////////////////////////////////////////////// // Declaration of the nested class SourceCopy @@ -101,17 +102,17 @@ class QOpenGLPlotter : public QGLWidget /** Temporary buffer for source information. * This class is used to extract certain information for each source from - * the Scene. There is no use in copying data which are not used afterwards. + * the scene. There is no use in copying data which are not used afterwards. **/ struct SourceCopy : DirectionalPoint { /// SourceCopies want to be stored in such a list typedef std::list list_t; /// type conversion constructor - SourceCopy(const Scene::source_map_t::value_type& other); + SourceCopy(const std::pair& other); - ssr::id_t id; ///< identifier - Source::model_t model; ///< source model + unsigned int id; ///< identifier + LegacySource::model_t model; ///< source model bool mute; ///< mute state float gain; ///< source gain float signal_level; ///< level of audio stream (linear, between 0 and 1) @@ -121,7 +122,7 @@ class QOpenGLPlotter : public QGLWidget }; public: - QOpenGLPlotter(Publisher& controller, const Scene& scene + QOpenGLPlotter(api::Publisher& controller, const LegacyScene& scene , const std::string& path_to_gui_images , QWidget *parent = 0); virtual ~QOpenGLPlotter(); @@ -134,19 +135,19 @@ class QOpenGLPlotter : public QGLWidget void set_device_pixel_ratio(); protected: - Publisher& _controller; - const Scene& _scene; + api::Publisher& _controller; + const LegacyScene& _scene; int _active_source; const std::string _path_to_gui_images; - ssr::id_t _id_of_last_clicked_source; + unsigned int _id_of_last_clicked_source; - typedef std::map selected_sources_map_t; + typedef std::map selected_sources_map_t; selected_sources_map_t _selected_sources_map; float _zoom_factor; - std::set _soloed_sources; + std::set _soloed_sources; // void mousePressEvent(QMouseEvent *event); QMouseEvent _previous_mouse_event; @@ -190,7 +191,7 @@ class QOpenGLPlotter : public QGLWidget GLuint _listener_shadow_texture; GLuint _listener_background_texture; - Loudspeaker::container_t _loudspeakers; + LegacyLoudspeaker::container_t _loudspeakers; color_vector_t _color_vector; diff --git a/src/gui/qsourceproperties.cpp b/src/gui/qsourceproperties.cpp index 1ff245da..a41bcbc1 100644 --- a/src/gui/qsourceproperties.cpp +++ b/src/gui/qsourceproperties.cpp @@ -288,7 +288,7 @@ QLabel* QSourceProperties::_create_text_label(const QString& text) return buffer; } -void QSourceProperties::update_displays(const Source& source, +void QSourceProperties::update_displays(const LegacySource& source, const DirectionalPoint& reference) { // do not update display if dialog is used to create a new sound source @@ -322,10 +322,10 @@ void QSourceProperties::update_displays(const Source& source, _position_fix_box->setChecked(source.fixed_position); // set source model switch(source.model){ - case Source::point: + case LegacySource::point: _source_model_display->setCurrentIndex(1); break; - case Source::plane: + case LegacySource::plane: _source_model_display->setCurrentIndex(0); break; default: diff --git a/src/gui/qsourceproperties.h b/src/gui/qsourceproperties.h index c6798551..ff476c52 100644 --- a/src/gui/qsourceproperties.h +++ b/src/gui/qsourceproperties.h @@ -40,7 +40,7 @@ #include #include "qclicktextlabel.h" -#include "source.h" +#include "legacy_source.h" /// QSourceProperties class QSourceProperties : public QFrame @@ -51,7 +51,7 @@ class QSourceProperties : public QFrame QSourceProperties(QWidget* parent = 0); ~QSourceProperties(); - void update_displays(const Source& source, + void update_displays(const LegacySource& source, const DirectionalPoint& reference); private: diff --git a/src/gui/quserinterface.cpp b/src/gui/quserinterface.cpp index 032fda58..e457d37e 100644 --- a/src/gui/quserinterface.cpp +++ b/src/gui/quserinterface.cpp @@ -49,6 +49,7 @@ using apf::math::linear2dB; #include "apf/stringtools.h" #include "posixpathtools.h" +#include "legacy_scene.h" // for LegacyScene #define FILEMENUWIDTH 128 #define BETWEENBUTTONSPACE 6 @@ -81,7 +82,8 @@ using apf::math::linear2dB; * @param parent parent Qt widget. If left as \a NULL (default) then the window * is startet as an independent main window. **/ -ssr::QUserInterface::QUserInterface(Publisher& controller, const Scene& scene +ssr::QUserInterface::QUserInterface(api::Publisher& controller + , const LegacyScene& scene , const std::string& path_to_gui_images , const std::string& path_to_scene_menu , unsigned int update_frequency @@ -92,7 +94,7 @@ ssr::QUserInterface::QUserInterface(Publisher& controller, const Scene& scene _controlsParent(this) { // set window title - std::string type = _controller.get_renderer_name(); + std::string type = _scene.get_renderer_name(); if (type == "wfs") setWindowTitle("SSR - WFS"); else if (type == "binaural") setWindowTitle("SSR - Binaural"); else if (type == "brs") setWindowTitle("SSR - BRS"); @@ -268,7 +270,7 @@ ssr::QUserInterface::~QUserInterface() /// Skips back to the beginning of the scene void ssr::QUserInterface::_skip_back() { - _controller.transport_locate(0); + _transport_locate(0); } /** Skips the scene to a specified instant of time @@ -278,7 +280,7 @@ void ssr::QUserInterface::_transport_locate(float time) { if (time >= 0.0f) { - _controller.transport_locate(time); + _controller.take_control()->transport_locate(time); } else { @@ -294,13 +296,15 @@ void ssr::QUserInterface::_solo_selected_sources() { _soloed_sources.clear(); + auto control = _controller.take_control(); + for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { _soloed_sources.insert(i->second); // make sure it's not muted - _controller.set_source_mute(i->second, false); + control->source_mute(_scene.string_id(i->second), false); } // for @@ -316,7 +320,7 @@ void ssr::QUserInterface::_solo_selected_sources() if (_soloed_sources.find(i->id) == _soloed_sources.end()) { // then mute it - _controller.set_source_mute(i->id, true); + control->source_mute(_scene.string_id(i->id), true); } } @@ -340,11 +344,12 @@ void ssr::QUserInterface::_unsolo_selected_sources() // if other sources are soloed else { + auto control = _controller.take_control(); for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // then mute the unsoloed sources - _controller.set_source_mute(i->second, true); + control->source_mute(_scene.string_id(i->second), true); } // for } @@ -358,36 +363,30 @@ void ssr::QUserInterface::_unsolo_all_sources() source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); + auto control = _controller.take_control(); for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { - _controller.set_source_mute(i->id, false); + control->source_mute(_scene.string_id(i->id), false); } } /// This slot is called when the \a processing \a button was clicked by the user. void ssr::QUserInterface::_processing_button_pressed() { - if(_scene.get_processing_state()) - { - _controller.stop_processing(); - } - else - { - _controller.start_processing(); - } + _controller.take_control()->processing(!_scene.get_processing_state()); } /// This slot is called when the \a pause \a button was clicked by the user. void ssr::QUserInterface::_pause_button_pressed() { - _controller.transport_stop(); + _controller.take_control()->transport_stop(); } /// This slot is called when the \a play \a button was clicked by the user. void ssr::QUserInterface::_play_button_pressed() { - _controller.transport_start(); + _controller.take_control()->transport_start(); } /** This function is called whenever the fiel menu actions (open/close etc.) @@ -521,7 +520,7 @@ void ssr::QUserInterface::_set_master_volume(float volume) volume = std::max(volume, MINVOLUME); // convert to linear scale - _controller.set_master_volume(apf::math::dB2linear(volume)); + _controller.take_control()->master_volume(apf::math::dB2linear(volume)); } /** Changes selected sources' volume. @@ -529,6 +528,7 @@ void ssr::QUserInterface::_set_master_volume(float volume) */ void ssr::QUserInterface::_change_volume_of_selected_sources(float d_volume) { + auto control = _controller.take_control(); for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { @@ -538,7 +538,7 @@ void ssr::QUserInterface::_change_volume_of_selected_sources(float d_volume) current_gain = std::min(current_gain, dB2linear(MAXVOLUME)); current_gain = std::max(current_gain, dB2linear(MINVOLUME)); - _controller.set_source_gain(i->second, current_gain); + control->source_volume(_scene.string_id(i->second), current_gain); } // for } @@ -565,7 +565,7 @@ void ssr::QUserInterface::_save_file_as() file_name_std.append(".asd"); } - _controller.save_scene_as_XML(file_name_std); + _controller.take_control()->save_scene(file_name_std); VERBOSE("Scene saved in '" << file_name_std << "'."); } @@ -601,7 +601,7 @@ void ssr::QUserInterface::_load_scene(const QString& path_to_scene) } } - _controller.load_scene(std::string(path_to_scene.toUtf8())); + _controller.take_control()->load_scene(std::string(path_to_scene.toUtf8())); // clear mouse cursor setCursor(Qt::ArrowCursor); @@ -741,7 +741,7 @@ void ssr::QUserInterface::_resizeControls(int newWidth) */ void ssr::QUserInterface::mousePressEvent(QMouseEvent *event) { - ssr::id_t _id_of_lastlast_clicked_source = _id_of_last_clicked_source; + unsigned int _id_of_lastlast_clicked_source = _id_of_last_clicked_source; event->accept(); @@ -870,25 +870,27 @@ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) Position d_position = position - *_scene.get_source_position(_id_of_last_clicked_source); + auto control = _controller.take_control(); + // move all selected sources for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // rotate complex sources by the appropriate angle - if (_scene.get_source_model(i->second) == Source::directional || - _scene.get_source_model(i->second) == Source::extended) + if (_scene.get_source_model(i->second) == LegacySource::directional || + _scene.get_source_model(i->second) == LegacySource::extended) { // position delta expressed as angle Orientation d_orientation = (*_scene.get_source_position(i->second) + d_position).orientation() - (*_scene.get_source_position(i->second)).orientation(); // set the new orientation - _controller.set_source_orientation(i->second, + control->source_rotation(_scene.string_id(i->second), (*_scene.get_source_orientation(i->second)) + d_orientation); } // if // finally set the source's position // plane waves and point sources will automatically face the reference - _controller.set_source_position(i->second, + control->source_position(_scene.string_id(i->second), *_scene.get_source_position(i->second) + d_position); } // for @@ -918,14 +920,16 @@ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) // previous position relative to source position prev_mouse_pos -= *_scene.get_source_position(_id_of_last_clicked_source); + auto control = _controller.take_control(); + // rotate all selected sources that can be rotated for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { - if (_scene.get_source_model(i->second) == Source::directional || - _scene.get_source_model(i->second) == Source::extended) + if (_scene.get_source_model(i->second) == LegacySource::directional || + _scene.get_source_model(i->second) == LegacySource::extended) { - _controller.set_source_orientation(i->second, + control->source_rotation(_scene.string_id(i->second), (*_scene.get_source_orientation(i->second)) + (mouse_pos.orientation() - prev_mouse_pos.orientation())); } @@ -956,7 +960,7 @@ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) // change gain if (gain > MINVOLUME && gain <= MAXVOLUME) { - _controller.set_source_gain(i->second, dB2linear(gain)); + _controller.take_control()->source_volume(_scene.string_id(i->second), dB2linear(gain)); } } // for @@ -985,7 +989,7 @@ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) // previous position relative to relative position prev_mouse_pos -= _scene.get_reference().position; - _controller.set_reference_orientation(_scene.get_reference().orientation + + _controller.take_control()->reference_rotation(_scene.get_reference().orientation + (mouse_pos.orientation() - prev_mouse_pos.orientation())); } // else if @@ -1001,7 +1005,7 @@ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) position.x = static_cast(pos_x + _x_offset); position.y = static_cast(pos_y + _y_offset); - _controller.set_reference_position(position); + _controller.take_control()->reference_position(position); } // else if // right click on background @@ -1128,8 +1132,8 @@ void ssr::QUserInterface::keyPressEvent(QKeyEvent *event) case Qt::Key_Down: _window_y_offset += 0.1f; update(); break; case Qt::Key_Left: _window_x_offset += 0.1f; update(); break; case Qt::Key_Right: _window_x_offset -= 0.1f; update(); break; - case Qt::Key_Space: if (_scene.is_playing()) _controller.transport_stop(); - else _controller.transport_start(); + case Qt::Key_Space: if (_scene.is_playing()) _controller.take_control()->transport_stop(); + else _controller.take_control()->transport_start(); break; case Qt::Key_Backspace: _skip_back(); break; @@ -1138,7 +1142,7 @@ void ssr::QUserInterface::keyPressEvent(QKeyEvent *event) case Qt::Key_F: _toggle_fixation_state_of_selected_sources(); break; case Qt::Key_M: _toggle_mute_state_of_selected_sources(); break; case Qt::Key_P: _toggle_source_models(); break; - case Qt::Key_R: _controller.set_auto_rotation(!_scene.get_auto_rotation()); break; + case Qt::Key_R: _controller.take_control()->auto_rotate_sources(!_scene.get_auto_rotation()); break; case Qt::Key_S: if ( event->modifiers() == Qt::ControlModifier ) { @@ -1185,7 +1189,7 @@ void ssr::QUserInterface::keyPressEvent(QKeyEvent *event) // change selected sources level else _change_volume_of_selected_sources(-1.0f); break; - case Qt::Key_Return: {_controller.calibrate_client(); break; } + case Qt::Key_Return: {_controller.take_control()->calibrate_tracker(); break; } case Qt::Key_Control: {_ctrl_pressed = true; break; } case Qt::Key_Alt: {_alt_pressed = true; break; } case Qt::Key_F11: {if (!isFullScreen()) setWindowState(Qt::WindowFullScreen); @@ -1294,17 +1298,18 @@ void ssr::QUserInterface::_show_about_window() */ void ssr::QUserInterface::_set_source_mute(const bool flag) { - _controller.set_source_mute(_id_of_last_clicked_source, flag); + _controller.take_control()->source_mute(_scene.string_id(_id_of_last_clicked_source), flag); } /// Toggles the mute state of all selected sound sources void ssr::QUserInterface::_toggle_mute_state_of_selected_sources() { + auto control = _controller.take_control(); // iterate over selected sources for ( selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { - _controller.set_source_mute(i->second, + control->source_mute(_scene.string_id(i->second), !_scene.get_source_mute_state(i->second)); } } @@ -1315,6 +1320,8 @@ void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); + auto control = _controller.take_control(); + for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { @@ -1323,7 +1330,7 @@ void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() // solo source _soloed_sources.insert(i->second); // make sure it's not muted - _controller.set_source_mute(i->second, false); + control->source_mute(_scene.string_id(i->second), false); // mute other sources for (source_buffer_list_t::const_iterator j = source_buffer_list.begin(); @@ -1333,7 +1340,7 @@ void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() if (_soloed_sources.find(j->id) == _soloed_sources.end()) { // then mute it - _controller.set_source_mute(j->id, true); + control->source_mute(_scene.string_id(j->id), true); } } } @@ -1345,7 +1352,7 @@ void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() for (source_buffer_list_t::const_iterator j = source_buffer_list.begin(); j != source_buffer_list.end(); j++) { - _controller.set_source_mute(j->id, false); + control->source_mute(_scene.string_id(j->id), false); } } } // for @@ -1353,17 +1360,18 @@ void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() void ssr::QUserInterface::_toggle_source_models() { + auto control = _controller.take_control(); // toggle all source types between "plane" and "point" for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // if source is plane then make sure that it faces the reference - if (_scene.get_source_model(i->second) == Source::plane) + if (_scene.get_source_model(i->second) == LegacySource::plane) { - _controller.set_source_model(i->second, Source::point); + control->source_model(_scene.string_id(i->second), apf::str::A2S(LegacySource::point)); } - else if (_scene.get_source_model(i->second) == Source::point) + else if (_scene.get_source_model(i->second) == LegacySource::point) { - _controller.set_source_model(i->second, Source::plane); + control->source_model(_scene.string_id(i->second), apf::str::A2S(LegacySource::plane)); } // if } // for } @@ -1373,7 +1381,7 @@ void ssr::QUserInterface::_toggle_source_models() */ void ssr::QUserInterface::_set_source_position_fixed(const bool flag) { - _controller.set_source_position_fixed(_id_of_last_clicked_source, flag); + _controller.take_control()->source_fixed(_scene.string_id(_id_of_last_clicked_source), flag); } /** Sets the position fixed state of the currently active mouse. @@ -1381,32 +1389,34 @@ void ssr::QUserInterface::_set_source_position_fixed(const bool flag) */ void ssr::QUserInterface::_set_source_model(const int index) { - Source::model_t model = Source::unknown; + LegacySource::model_t model = LegacySource::unknown; switch(index){ case 0: - model = Source::plane; + model = LegacySource::plane; break; case 1: - model = Source::point; + model = LegacySource::point; break; } VERBOSE("index: " << index); - ssr::id_t id = _id_of_last_clicked_source; - _controller.set_source_model(id, model); + unsigned int id = _id_of_last_clicked_source; + auto control = _controller.take_control(); + control->source_model(_scene.string_id(id), apf::str::A2S(model)); // make sure that the plane wave is oriented towards the reference point - _controller.set_source_orientation(id, (_scene.get_reference().position + control->source_rotation(_scene.string_id(id), (_scene.get_reference().position - *_scene.get_source_position(id)).orientation()); } void ssr::QUserInterface::_toggle_fixation_state_of_selected_sources() { + auto control = _controller.take_control(); // iterate over selected sources for ( selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { - _controller.set_source_position_fixed(i->second, + control->source_fixed(_scene.string_id(i->second), !_scene.get_source_position_fixed(i->second)); } } diff --git a/src/gui/quserinterface.h b/src/gui/quserinterface.h index 42ff15fb..ddd11b3f 100644 --- a/src/gui/quserinterface.h +++ b/src/gui/quserinterface.h @@ -60,7 +60,7 @@ class QUserInterface : public QOpenGLPlotter Q_OBJECT public: - QUserInterface(Publisher& controller, const Scene& scene + QUserInterface(api::Publisher& controller, const LegacyScene& scene , const std::string& path_to_gui_images , const std::string& path_to_scene_menu , unsigned int update_frequency = 30u, QWidget *parent = 0); diff --git a/src/directionalpoint.cpp b/src/legacy_directionalpoint.cpp similarity index 99% rename from src/directionalpoint.cpp rename to src/legacy_directionalpoint.cpp index 5a68be02..6b46e4ee 100644 --- a/src/directionalpoint.cpp +++ b/src/legacy_directionalpoint.cpp @@ -30,7 +30,7 @@ #include #include // for cos() -#include "directionalpoint.h" +#include "legacy_directionalpoint.h" /** ctor. * @param position position diff --git a/src/directionalpoint.h b/src/legacy_directionalpoint.h similarity index 96% rename from src/directionalpoint.h rename to src/legacy_directionalpoint.h index f30527a1..633433bd 100644 --- a/src/directionalpoint.h +++ b/src/legacy_directionalpoint.h @@ -27,13 +27,13 @@ /// @file /// Geometry information of a point including an orientation (definition). -#ifndef SSR_DIRECTIONALPOINT_H -#define SSR_DIRECTIONALPOINT_H +#ifndef SSR_LEGACY_DIRECTIONALPOINT_H +#define SSR_LEGACY_DIRECTIONALPOINT_H #include -#include "position.h" -#include "orientation.h" +#include "legacy_position.h" +#include "legacy_orientation.h" /** Class which combines Position and Orientation. * Anything which has a position and orientation can be derived from it. diff --git a/src/loudspeaker.h b/src/legacy_loudspeaker.h similarity index 82% rename from src/loudspeaker.h rename to src/legacy_loudspeaker.h index dfc0b979..77ae3916 100644 --- a/src/loudspeaker.h +++ b/src/legacy_loudspeaker.h @@ -25,20 +25,21 @@ ******************************************************************************/ /// @file -/// %Loudspeaker class (definition). +/// Legacy loudspeaker class (definition). Superseded by Loudspeaker. -#ifndef SSR_LOUDSPEAKER_H -#define SSR_LOUDSPEAKER_H +#ifndef SSR_LEGACY_LOUDSPEAKER_H +#define SSR_LEGACY_LOUDSPEAKER_H #include #include -#include "directionalpoint.h" +#include "api.h" // for Loudspeaker +#include "legacy_directionalpoint.h" /// Class for saving loudspeaker information. -struct Loudspeaker : DirectionalPoint +struct LegacyLoudspeaker : DirectionalPoint { - typedef std::vector container_t; + typedef std::vector container_t; /// %Loudspeaker type. enum model_t @@ -50,29 +51,31 @@ struct Loudspeaker : DirectionalPoint }; /// ctor - explicit Loudspeaker( + explicit LegacyLoudspeaker( const DirectionalPoint& point = DirectionalPoint(), model_t model = normal, float weight = 1.0f, float delay = 0.0f) : DirectionalPoint(point), // base class copy ctor model(model), weight(weight), - delay(delay), - mute(false), - active(false) + delay(delay) {} - // auto-generated copy ctor and assignment operator are OK. + /// Constructor from "modern" Loudspeaker. + LegacyLoudspeaker(const ssr::Loudspeaker& other) + : LegacyLoudspeaker{{other.position, other.rotation} + , other.model == "subwoofer" ? subwoofer : normal} + {} + + operator ssr::Loudspeaker() + { + return {this->position, this->orientation + , this->model == subwoofer ? "subwoofer" : ""}; + } - //std::string name; - //std::string audio_file_name; - //std::string audio_file_channel; - //std::string port_name; model_t model; ///< type of loudspeaker float weight; /// linear! float delay; /// in seconds - bool mute; ///< mute/unmute - bool active; ///< active/inactive for a given source - //float gain; ///< gain + bool mute{false}; ///< mute/unmute friend std::istream& operator>>(std::istream& input, model_t& model) { diff --git a/src/orientation.cpp b/src/legacy_orientation.cpp similarity index 83% rename from src/orientation.cpp rename to src/legacy_orientation.cpp index 19387337..e526ceb7 100644 --- a/src/orientation.cpp +++ b/src/legacy_orientation.cpp @@ -25,19 +25,46 @@ ******************************************************************************/ /// @file -/// %Orientation class and helper function(s) (implementation). +/// Legacy 2D %Orientation class and helper function(s) (implementation). #include -#include "orientation.h" +#include "legacy_orientation.h" #include "apf/math.h" #include "ssr_global.h" +namespace +{ + // Convert 2D azimuth in radians to 3D rotation. + ssr::Rot azi2quat(float azimuth) + { + return {0, 0, std::sin(azimuth / 2), std::cos(azimuth / 2)}; + } + + // Convert 3D rotation to 2D azimuth in radians. + // NB: This doesn't take care of Gimbal Lock! + float quat2azi(ssr::Rot rot) + { + auto [x, y, z, w] = rot; + return std::atan2(2.0f * (w * z + x * y), 1.0f - 2.0f * (y * y + z * z)); + } +} + /// ctor. @param azimuth azimuth (in degrees) Orientation::Orientation(const float azimuth) : azimuth(azimuth) {} +/// Convert from 3D rotation. +Orientation::Orientation(const ssr::Rot& three_d_rot) : + azimuth(apf::math::rad2deg(quat2azi(three_d_rot)) + 90.0f) +{} + +Orientation::operator ssr::Rot() +{ + return azi2quat(apf::math::deg2rad(this->azimuth - 90.0f)); +} + /** - operator. * @return difference of Orientations **/ diff --git a/src/orientation.h b/src/legacy_orientation.h similarity index 93% rename from src/orientation.h rename to src/legacy_orientation.h index 001d6a04..9cf67d12 100644 --- a/src/orientation.h +++ b/src/legacy_orientation.h @@ -25,21 +25,26 @@ ******************************************************************************/ /// @file -/// %Orientation class and helper function(s) (definition). +/// Legacy 2D %Orientation class and helper function(s) (definition). -#ifndef SSR_ORIENTATION_H -#define SSR_ORIENTATION_H +#ifndef SSR_LEGACY_ORIENTATION_H +#define SSR_LEGACY_ORIENTATION_H #include +#include "api.h" // for Rot + /** Geometric representation of a orientation. * For now, only azimuth value is handled. **/ struct Orientation { - // the default orientation is in negative y-direction (facing the listener) explicit Orientation(const float azimuth = 0); + Orientation(const ssr::Rot& three_d_rot); + + operator ssr::Rot(); + float azimuth; ///< (=yaw) azimuth (in degrees) /// plus (+) operator diff --git a/src/position.cpp b/src/legacy_position.cpp similarity index 93% rename from src/position.cpp rename to src/legacy_position.cpp index 96265515..30aba85f 100644 --- a/src/position.cpp +++ b/src/legacy_position.cpp @@ -25,13 +25,13 @@ ******************************************************************************/ /// @file -/// %Position class and helper functions (implementation). +/// Legacy 2D %Position class and helper functions (implementation). #include // for atan2(), sqrt() #include -#include "position.h" -#include "orientation.h" +#include "legacy_position.h" +#include "legacy_orientation.h" #include "apf/math.h" Position::Position(const float x, const float y) : @@ -39,6 +39,16 @@ Position::Position(const float x, const float y) : y(y) {} +Position::Position(const ssr::Pos& three_d_pos) : + x(three_d_pos.x), + y(three_d_pos.y) +{} + +Position::operator ssr::Pos() +{ + return {this->x, this->y}; +} + Position& Position::operator+=(const Position& other) { x += other.x; diff --git a/src/position.h b/src/legacy_position.h similarity index 93% rename from src/position.h rename to src/legacy_position.h index 8ffca92b..224f3fa5 100644 --- a/src/position.h +++ b/src/legacy_position.h @@ -25,12 +25,13 @@ ******************************************************************************/ /// @file -/// %Position class and helper functions (definition). +/// Legacy 2D %Position class and helper functions (definition). -#ifndef SSR_POSITION_H -#define SSR_POSITION_H +#ifndef SSR_LEGACY_POSITION_H +#define SSR_LEGACY_POSITION_H -#include "orientation.h" +#include "api.h" // for Pos +#include "legacy_orientation.h" /** Geometric representation of a position. * Stores the position of a point in space and provides some helper functions. @@ -48,6 +49,11 @@ struct Position **/ explicit Position(const float x = 0, const float y = 0); + /// Conversion from 3D position. + Position(const ssr::Pos& three_d_pos); + + operator ssr::Pos(); + float x; ///< x coordinate (in meters) float y; ///< y coordinate (in meters) diff --git a/src/legacy_scene.cpp b/src/legacy_scene.cpp new file mode 100644 index 00000000..c2bd5203 --- /dev/null +++ b/src/legacy_scene.cpp @@ -0,0 +1,212 @@ +/****************************************************************************** + * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the SoundScape Renderer (SSR). * + * * + * The SSR is free software: you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * The SSR 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program. If not, see . * + * * + * The SSR is a tool for real-time spatial audio reproduction providing a * + * variety of rendering algorithms. * + * * + * http://spatialaudio.net/ssr ssr@spatialaudio.net * + ******************************************************************************/ + +/// @file +/// Legacy scene class (implementation), superseded by Scene. + +#include + +#include "legacy_scene.h" +#include "legacy_source.h" + +ssr::LegacyScene::LegacyScene() : + // reference looking straight ahead (in positive y-direction) + _reference(Position(0, 0), Orientation(90)), + _reference_offset(Position(0, 0), Orientation(0)), + _master_volume(1.0f), + _decay_exponent(1.0f), + _amplitude_reference_distance(3.0f), + _master_signal_level(0.0f), + _cpu_load(0.0f), _sample_rate(0u), + _processing_state(true), + _auto_rotate_sources(true) +{} + +ssr::LegacyScene::loudspeakers_t::size_type ssr::LegacyScene::get_number_of_loudspeakers() const +{ + return _loudspeakers.size(); +} + + +/// _. @return processing state +bool ssr::LegacyScene::get_processing_state() const +{ + return _processing_state; +} + +/// _. @return master volume +float ssr::LegacyScene::get_master_volume() const +{ + return _master_volume; +} + +/// _. @return amplitude decay exponent +float ssr::LegacyScene::get_decay_exponent() const +{ + return _decay_exponent; +} + +/// _. @return amplitude reference distance +float ssr::LegacyScene::get_amplitude_reference_distance() const +{ + return _amplitude_reference_distance; +} + +/// _. @return instantaneous overall audio signal level +float ssr::LegacyScene::get_master_signal_level() const +{ + return _master_signal_level; +} + +/// _. @return CPU load in percent +float ssr::LegacyScene::get_cpu_load() const +{ + return _cpu_load; +} + +/// _. @return current sample rate +int ssr::LegacyScene::get_sample_rate() const +{ + return _sample_rate; +} + + +bool ssr::LegacyScene::is_playing() const +{ + //return _transport_state.playing; + return _transport_playing; +} + +uint32_t ssr::LegacyScene::get_transport_position() const +{ + //return _transport_state.position; + return _transport_position; +} + +bool ssr::LegacyScene::get_auto_rotation() const +{ + return _auto_rotate_sources; +} + +LegacySource ssr::LegacyScene::get_source(legacy_id_t id) const +{ + auto source = LegacySource(_loudspeakers.size()); + auto source_ptr = maptools::get_item(_source_map, id); + + if (!source_ptr) ERROR("Source " << id << " doesn't exist!"); + else source = *source_ptr; + + return source; +} + +/** _. + * @param id ID of the source + * @return position of the source + * @warning If @a id is not found, a unique_ptr to NULL is returned! + **/ +std::unique_ptr ssr::LegacyScene::get_source_position(legacy_id_t id) const +{ + std::unique_ptr position(new Position); // standard ctor + if (_get_source_member(id, &LegacySource::position, *position)) return position; + position.reset(); // set to NULL + return position; +} + +/** _. + * @param id ID of the source + * @return position of the source + * @warning If @a id is not found, a unique_ptr to NULL is returned! + **/ +std::unique_ptr ssr::LegacyScene::get_source_orientation(legacy_id_t id) const +{ + std::unique_ptr orientation(new Orientation); // standard ctor + if (_get_source_member(id, &LegacySource::orientation, *orientation)) return orientation; + orientation.reset(); // set to NULL + return orientation; +} + +/** _. + * @param id ID of the source + * @return source model + * @warning If @a id is not found, LegacySource::unknown is returned + **/ +LegacySource::model_t ssr::LegacyScene::get_source_model(legacy_id_t id) const +{ + LegacySource::model_t model; + if (_get_source_member(id, &LegacySource::model, model)) return model; + return LegacySource::unknown; +} + +/** _. + * @param id ID of the source + * @return linear source gain (=volume) + * @warning If @a id is not found, 0 is returned + **/ +float ssr::LegacyScene::get_source_gain(legacy_id_t id) const +{ + float gain; + if (_get_source_member(id, &LegacySource::gain, gain)) return gain; + return 0; +} + +/** _. + * @param id ID of the source + * @return mute state + * @warning If @a id is not found, false is returned + **/ +bool ssr::LegacyScene::get_source_mute_state(legacy_id_t id) const +{ + bool state; + if (_get_source_member(id, &LegacySource::mute, state)) return state; + return false; +} + +/** _. + * @param id ID of the source + * @return source name + * @warning If @a id is not found, an empty string is returned + **/ +std::string ssr::LegacyScene::get_source_name(legacy_id_t id) const +{ + std::string name; + if (_get_source_member(id, &LegacySource::name, name)) return name; + return std::string(""); +} + +bool ssr::LegacyScene::get_source_position_fixed(legacy_id_t id) const +{ + bool boolean; + if (_get_source_member(id, &LegacySource::fixed_position, boolean)) return boolean; + + return false; +} + +std::string ssr::LegacyScene::get_source_properties_file(legacy_id_t id) const +{ + std::string name; + if (_get_source_member(id, &LegacySource::properties_file, name)) return name; + return std::string(""); +} diff --git a/src/legacy_scene.h b/src/legacy_scene.h new file mode 100644 index 00000000..daed43d7 --- /dev/null +++ b/src/legacy_scene.h @@ -0,0 +1,562 @@ +/****************************************************************************** + * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the SoundScape Renderer (SSR). * + * * + * The SSR is free software: you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * The SSR 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program. If not, see . * + * * + * The SSR is a tool for real-time spatial audio reproduction providing a * + * variety of rendering algorithms. * + * * + * http://spatialaudio.net/ssr ssr@spatialaudio.net * + ******************************************************************************/ + +/// @file +/// Legacy scene class (definition), superseded by Scene. + +#ifndef SSR_LEGACY_SCENE_H +#define SSR_LEGACY_SCENE_H + +#include +#include +#include // for assert() +#include + +#include "apf/stringtools.h" // for S2RV() +#include "api.h" +#include "maptools.h" // for get_item() +#include "legacy_source.h" +#include "legacy_loudspeaker.h" +#include "ssr_global.h" // for VERBOSE() + +namespace ssr +{ + +/// Legacy scene. Contains current location and other data about sources, the +/// reference point and other things. +class LegacyScene : public api::SceneControlEvents + , public api::SceneInformationEvents + , public api::RendererControlEvents + , public api::RendererInformationEvents + , public api::TransportEvents + , public api::SourceMetering + , public api::MasterMetering + , public api::OutputActivity + , public api::CpuLoad +{ + public: + using legacy_id_t = unsigned int; + + /// A map of sources. + using source_map_t = std::map; + /// A vector of loudspeakers. + using loudspeakers_t = std::vector; + + LegacyScene(); ///< ctor + + loudspeakers_t::size_type get_number_of_loudspeakers() const; + + /// get master volume + float get_master_volume() const; + + /// get master volume with amplitude correction considered + float get_corrected_master_volume() const; + + /// get amplitude decay exponent + float get_decay_exponent() const; + + /// get amplitude reference distance + float get_amplitude_reference_distance() const; + + /// get master audio level + float get_master_signal_level() const; + + /// get CPU load in percent estimated by JACK + float get_cpu_load() const; + + /// get current sample rate + int get_sample_rate() const; + + /// get source + LegacySource get_source(legacy_id_t id) const; + /// get source position + std::unique_ptr get_source_position(legacy_id_t id) const; + /// get source orientation + std::unique_ptr get_source_orientation(legacy_id_t id) const; + /// get source model (=type) + LegacySource::model_t get_source_model(legacy_id_t id) const; + /// get source gain + float get_source_gain(legacy_id_t id) const; + /// get source mute state + bool get_source_mute_state(legacy_id_t id) const; + /// get source name + std::string get_source_name(legacy_id_t id) const; + + bool get_source_position_fixed(legacy_id_t id) const; + + std::string get_source_properties_file(legacy_id_t id) const; + + /// check if renderer is processing + bool get_processing_state() const; + bool is_playing() const; + uint32_t get_transport_position() const; + + bool get_auto_rotation() const; + + // temporarily with inline implementation + // should return 0 in case of doubt. + // will be removed in the near future + /// deprecated! + inline virtual int get_max_no_of_sources() const + { + return 100; + } + + /// get a list of all sources. + /// @param container (initially empty) list of sources. + /// @pre + /// For the template argument @a T you can use any type which has a + /// conversion constructor for the LegacySource type. Speaking more exactly, you + /// need a constructor of the following form: + /// T::T(const pair&) + /// @par + /// Your type @a T can include any of the members of LegacySource, this way you + /// can obtain any subset of data you desire. If you want all available + /// data, just use the LegacySource type itself. + /// @warning if a std::vector is used as container, the function + /// get_number_of_sources() is called to reserve the + /// necessary memory to avoid memory re-allocation. + /// + /// Because a fancy template template is used, any container type with one + /// template argument can be used, like std::list, std::vector, ... + /// as long as it has the following member functions: .begin(), .end(), + /// .push_back(). + template class Container, typename T, + typename... Args> + void get_sources(Container& container) const + { + assert(container.empty()); + + // the following struct container_traits is declared in the private part + // of the LegacyScene class and defined further down this file. + + if (container_traits>::has_reserve) + { + container_traits>::reserve(container, + _source_map.size()); + } + for (const auto& source: _source_map) + { + // type conversion constructor T::T(const pair&) needed! + container.push_back(T(source)); + } + } + + + template + void for_each_source(F f) const + { + for (const auto& [id, source]: _source_map) { f(id, source); } + } + + + /// get a list of all loudspeakers. + /// @warning This doesn't return a valid value for the "active" field! (if + /// your type T has it) + /// + /// ID of the loudspeaker == its position in the container + /// container[0] has ID 1, container[1] has ID 2, ... + template class Container, typename T, + typename Allocator> + void get_loudspeakers(Container& container, + bool absolute_position = true) const + { + assert(container.empty()); + + if (container_traits>::has_reserve) + { + container_traits>::reserve(container, + _loudspeakers.size()); + } + + for (loudspeakers_t::const_iterator i = _loudspeakers.begin(); + i != _loudspeakers.end(); ++i) + { + T temp(*i); // copy ctor. is called + if (absolute_position) + { + // T has to be derived from DirectionalPoint + temp.DirectionalPoint::operator=(temp.transform(_reference)); + } + container.push_back(temp); // copy ctor. is called again + } + } + + /// get current reference position/orientation. + /// @return position/orientation of the reference point. + DirectionalPoint get_reference() const + { + return _reference; + } + + DirectionalPoint get_reference_offset() const + { + return _reference_offset; + } + + bool show_head() const + { + return _loudspeakers.empty(); + } + + std::string get_renderer_name() const + { + return _renderer_name; + } + + std::string string_id(legacy_id_t id) const + { + for (const auto& [str, num]: _source_id_map) + { + if (num == id) + { + return str; + } + } + throw std::runtime_error("Source doesn't exist"); + } + + protected: + source_map_t _source_map; ///< container for sources + /// Mapping from string IDs to legacy numeric IDs. + std::map> _source_id_map; + + private: + // from SceneControlEvents + + void auto_rotate_sources(bool auto_rotate) override + { + _auto_rotate_sources = auto_rotate; + } + + void delete_source(id_t id) override + { + if (auto iter = _source_id_map.find(id); iter != _source_id_map.end()) + { + // this should call the destructor for the LegacySource object. + // IMPORTANT: the map holds the Sources directly, no pointers! + _source_map.erase(iter->second); + } + } + + void source_position(id_t id, const Pos& position) override + { + _set_source_member(id, &LegacySource::position, position); + } + + void source_rotation(id_t id, const Rot& rotation) override + { + _set_source_member(id, &LegacySource::orientation, rotation); + } + + void source_volume(id_t id, float volume) override + { + _set_source_member(id, &LegacySource::gain, volume); + } + + void source_mute(id_t id, bool mute) override + { + _set_source_member(id, &LegacySource::mute, mute); + } + + void source_name(id_t id, const std::string& name) override + { + _set_source_member(id, &LegacySource::name, name); + } + + void source_model(id_t id, const std::string& model) override + { + _set_source_member(id, &LegacySource::model + , apf::str::S2RV(model, LegacySource::unknown)); + } + + void source_fixed(id_t id, bool fixed) override + { + _set_source_member(id, &LegacySource::fixed_position, fixed); + } + + void reference_position(const Pos& position) override + { + _reference.position = position; + } + + void reference_rotation(const Rot& rotation) override + { + _reference.orientation = rotation; + } + + void master_volume(float volume) override + { + _master_volume = volume; + } + + void decay_exponent(float exponent) override + { + _decay_exponent = exponent; + } + + void amplitude_reference_distance(float distance) override + { + _amplitude_reference_distance = distance; + } + + // from SceneInformationEvents + + void new_source(id_t id) override + { + auto [iter, inserted] = _source_id_map.try_emplace(id, _next_source_id); + if (inserted) + { + ++_next_source_id; + + VERBOSE("LegacyScene: Adding source \"" << iter->first + << "\" to source map"); + _source_map[iter->second] = LegacySource(_loudspeakers.size()); + } + else + { + WARNING("LegacyScene: Source ID \"" << id << "\" already exists"); + } + } + + virtual void source_property(id_t id, const std::string& key + , const std::string& value) override + { + if (key == "audio_file") + { + _set_source_member(id, &LegacySource::audio_file_name, value); + } + else if (key == "audio_file_channel") + { + _set_source_member(id, &LegacySource::audio_file_channel + , apf::str::S2RV(value, 0)); + } + else if (key == "audio_file_length") + { + _set_source_member(id, &LegacySource::file_length + , apf::str::S2RV(value, 0)); + } + else if (key == "port_name") + { + _set_source_member(id, &LegacySource::port_name, value); + } + else if (key == "properties_file") + { + _set_source_member(id, &LegacySource::properties_file, value); + } + else + { + WARNING("LegacyScene: Unknown property: \"" << key << "\""); + } + } + + // from RendererControlEvents + + void processing(bool processing_state) override + { + _processing_state = processing_state; + } + + void reference_offset_position(const Pos& position) override + { + _reference_offset.position = position; + } + + void reference_offset_rotation(const Rot& rotation) override + { + Orientation orientation(rotation); + orientation.azimuth -= 90.0f; // Undo angle conversion offset + _reference_offset.orientation = orientation; + } + + // from RendererInformationEvents + + void renderer_name(const std::string& name) override + { + _renderer_name = name; + } + + void sample_rate(int rate) override + { + _sample_rate = rate; + } + + void loudspeakers(const std::vector& loudspeakers) override + { + _loudspeakers.assign(loudspeakers.begin(), loudspeakers.end()); + } + + // from TransportEvents + + void transport_state(bool rolling, uint32_t frame) override + { + _transport_playing = rolling; + _transport_position = frame; + } + + // from SourceMetering + + void source_level(id_t id, float level) override + { + _set_source_member(id, &LegacySource::signal_level, level); + } + + // from MasterMetering + + void master_level(float level) override + { + _master_signal_level = level; + } + + // from OutputActivity + + void output_activity(id_t id, float* begin, float* end) override + { + if (auto iter = _source_id_map.find(id); iter != _source_id_map.end()) + { + if (auto source_ptr = maptools::get_item(_source_map, iter->second) + ; source_ptr) + { + assert(source_ptr); + if (source_ptr->output_levels.size() + == size_t(std::distance(begin, end))) + { + std::copy(begin, end, source_ptr->output_levels.begin()); + } + else + { + VERBOSE("LegacyScene: Wrong number of outputs"); + } + } + } + else + { + VERBOSE("LegacyScene: Source ID \"" << id << "\" doesn't exist"); + } + } + + // from CpuLoad + + void cpu_load(float load) override + { + _cpu_load = load; + } + + loudspeakers_t _loudspeakers; + DirectionalPoint _reference; ///< position/orientation of the reference + DirectionalPoint _reference_offset; + float _master_volume; ///< master volume (linear) + float _master_volume_correction; ///< dito (linear scale) + float _decay_exponent; ///< dito + float _amplitude_reference_distance; ///< distance where plane sources are + ///< as loud as the other source types + float _master_signal_level; ///< instantaneous overall signal level (linear) + float _cpu_load; ///< CPU load in percent + std::string _renderer_name; + int _sample_rate; ///< sample rate + /// order of mirror sources. if 0, mirror sources are deactivated + int mirror_order; + bool _processing_state; ///< is renderer processing? + bool _transport_playing; + uint32_t _transport_position; ///< current position in the audio file in samples + bool _auto_rotate_sources; + + template + void _set_source_member(id_t id, PointerToMember member, T&& arg) + { + if (auto iter = _source_id_map.find(id); iter != _source_id_map.end()) + { + if (auto source_ptr = maptools::get_item(_source_map, iter->second) + ; source_ptr) + { + source_ptr->*member = std::forward(arg); + return; + } + } + VERBOSE("LegacyScene: Source ID \"" << id << "\" doesn't exist!"); + } + + /// helper function template for get_*() + template bool _get_source_member( + legacy_id_t id, PointerToMember member, T& arg) const + { + const LegacySource* source_ptr = maptools::get_item(_source_map, id); + if (!source_ptr) + { + VERBOSE("LegacyScene: Source " << id << " doesn't exist!"); + return false; + } + arg = source_ptr->*member; + return true; + } + + // forward declaration of the container traits class + template struct container_traits; // nested + // partial specialization: + template struct container_traits>; + + unsigned int _next_source_id{1}; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Definition of the nested struct container_traits +//////////////////////////////////////////////////////////////////////////////// + +/// traits class to check if a container has a reserve() member function +// default template for any type of container +template struct LegacyScene::container_traits +{ + static const bool has_reserve = false; + template static void reserve(Container&, const U&) + { + // does nothing, because generally, there is no reserve() member + // function an we will never call it anyway. + // We only declare it to get no errors from the compiler. + } +}; + +/// traits class to check if a container has a reserve() member function +// partial specialization of the above template. +template +struct LegacyScene::container_traits> +{ + static const bool has_reserve = true; + template static void reserve(std::vector& v, const U& space) + { + v.reserve(space); + } +}; +// if any other STL container has a reserve() member function it should be +// added here as a specialization. + +// TODO: allow Allocator template argument with default std::allocator +// but: default template arguments may not be used in partial specializations +// introducing this argument into the default template as well would make this +// already quite complicated template thingy even more complicated! + +} // namespace ssr + +#endif diff --git a/src/source.h b/src/legacy_source.h similarity index 93% rename from src/source.h rename to src/legacy_source.h index 6b628202..7b7ed1eb 100644 --- a/src/source.h +++ b/src/legacy_source.h @@ -25,21 +25,21 @@ ******************************************************************************/ /// @file -/// %Source class (definition). +/// Legacy source class (definition). Superseded by Source. -#ifndef SSR_SOURCE_H -#define SSR_SOURCE_H +#ifndef SSR_LEGACY_SOURCE_H +#define SSR_LEGACY_SOURCE_H #include #include #include // for operator>> -#include "directionalpoint.h" +#include "legacy_directionalpoint.h" /// Class for saving source information -struct Source : DirectionalPoint +struct LegacySource : DirectionalPoint { - /** %Source type. + /** Legacy source type. * This enum can be extended arbitrarily, but for starters only point source * and plane wave will be implemented. **/ @@ -56,13 +56,13 @@ struct Source : DirectionalPoint /// ctor. /// @param position position /// @param orientation orientation - explicit Source(size_t outputs, + explicit LegacySource(size_t outputs, const Position& position = Position(), const Orientation& orientation = Orientation()) : DirectionalPoint(position, orientation), // base class ctor audio_file_channel(0), file_length(0), - model(Source::point), + model(LegacySource::point), mute(0), gain(1.0), signal_level(0.0), @@ -74,13 +74,13 @@ struct Source : DirectionalPoint {} // TODO: get rid of output levels? remove one of the constructors? - explicit Source( + explicit LegacySource( const Position& position = Position(), const Orientation& orientation = Orientation()) : DirectionalPoint(position, orientation), // base class ctor audio_file_channel(0), file_length(0), - model(Source::point), + model(LegacySource::point), mute(0), gain(1.0), signal_level(0.0), diff --git a/src/legacy_xmlsceneprovider.h b/src/legacy_xmlsceneprovider.h new file mode 100644 index 00000000..eb7f517a --- /dev/null +++ b/src/legacy_xmlsceneprovider.h @@ -0,0 +1,18 @@ +// For compatibility with old network interface + +#ifndef SSR_LEGACY_XMLSCENEPROVIDER_H +#define SSR_LEGACY_XMLSCENEPROVIDER_H + +namespace ssr +{ + +struct LegacyXmlSceneProvider +{ + virtual ~LegacyXmlSceneProvider() = default; + + virtual std::string get_scene_as_XML() const = 0; +}; + +} // namespace ssr + +#endif diff --git a/src/loudspeakerrenderer.h b/src/loudspeakerrenderer.h index 2a4cec01..aec1b9ee 100644 --- a/src/loudspeakerrenderer.h +++ b/src/loudspeakerrenderer.h @@ -31,7 +31,7 @@ #define SSR_LOUDSPEAKERRENDERER_H #include "rendererbase.h" -#include "loudspeaker.h" +#include "legacy_loudspeaker.h" #include "xmlparser.h" #include "apf/parameter_map.h" @@ -46,14 +46,14 @@ class LoudspeakerRenderer : public RendererBase using Node = XMLParser::Node; public: - class Output : public _base::Output, public Loudspeaker + class Output : public _base::Output, public LegacyLoudspeaker { public: - struct Params : _base::Output::Params, Loudspeaker {}; + struct Params : _base::Output::Params, LegacyLoudspeaker {}; // TODO: handle loudspeaker delays? - Output(const Params& p) : _base::Output(p), Loudspeaker(p) {} + Output(const Params& p) : _base::Output(p), LegacyLoudspeaker(p) {} }; LoudspeakerRenderer(const apf::parameter_map& p) @@ -67,7 +67,7 @@ class LoudspeakerRenderer : public RendererBase void load_reproduction_setup(); - void get_loudspeakers(std::vector& l) const; + void get_loudspeakers(std::vector& l) const; private: void _load_loudspeaker(const Node& node); @@ -87,7 +87,7 @@ class LoudspeakerRenderer : public RendererBase template void -LoudspeakerRenderer::get_loudspeakers(std::vector& l) +LoudspeakerRenderer::get_loudspeakers(std::vector& l) const { using out_list_t @@ -95,7 +95,7 @@ LoudspeakerRenderer::get_loudspeakers(std::vector& l) for (const auto& out: out_list_t(this->get_output_list())) { - l.push_back(Loudspeaker(out)); + l.push_back(LegacyLoudspeaker(out)); } } @@ -221,10 +221,10 @@ LoudspeakerRenderer::_load_loudspeaker(const Node& node) } std::string model_str = node.get_attribute("model"); - Loudspeaker::model_t model; - if (model_str == "normal") model = Loudspeaker::normal; - else if (model_str == "subwoofer") model = Loudspeaker::subwoofer; - else model = Loudspeaker::normal; + LegacyLoudspeaker::model_t model; + if (model_str == "normal") model = LegacyLoudspeaker::normal; + else if (model_str == "subwoofer") model = LegacyLoudspeaker::subwoofer; + else model = LegacyLoudspeaker::normal; float delay = !node ? 0.0f : apf::str::S2RV(node.get_attribute("delay"), 0.0f); diff --git a/src/network/commandparser.cpp b/src/network/commandparser.cpp index 67aaa0be..df5689bc 100644 --- a/src/network/commandparser.cpp +++ b/src/network/commandparser.cpp @@ -31,8 +31,10 @@ #include "ssr_global.h" // for ERROR() #include "commandparser.h" -#include "publisher.h" +#include "api.h" #include "xmlparser.h" +#include "legacy_position.h" +#include "legacy_orientation.h" #include "apf/stringtools.h" #include "apf/math.h" // for dB2linear() @@ -40,10 +42,10 @@ using namespace apf::str; /** ctor. - * @param controller + * @param publisher **/ -ssr::CommandParser::CommandParser(Publisher& controller) - : _controller(controller) +ssr::CommandParser::CommandParser(api::Publisher& publisher) + : _publisher(publisher) {} /** Parse a XML string and map to Controller. @@ -69,6 +71,8 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) } XMLParser::Node node1 = result1->node(); // get first (and only?) node. + auto control = _publisher.take_control(); + for (XMLParser::Node i = node1.child(); !!i; ++i) { if (i == "source") @@ -79,19 +83,25 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) new_source = false; } - id_t id = 0; // ugly ... + unsigned int source_number = 0; if (!new_source) { - if (!S2A(i.get_attribute("id"),id)) + if (!S2A(i.get_attribute("id"), source_number)) { ERROR("No source ID specified!"); return; } } + auto id = control->get_source_id(source_number); + if (id == "" && !new_source) + { + ERROR("Invalid source number: " << source_number); + return; + } Position position; Orientation orientation; - bool position_fixed = false, orientation_fixed = false; + bool fixed = false; for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { @@ -102,12 +112,12 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) { if (!new_source) { - _controller.set_source_position(id, position); + control->source_position(id, position); VERBOSE2("set source position: id = " << id << ", " << position); } else { - // position is used later for _controller.new_source(...) + // position is used later for control->new_source(...) } } else @@ -115,18 +125,18 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) position = Position(); } - if (S2A(inner_loop.get_attribute("fixed"), position_fixed)) + if (S2A(inner_loop.get_attribute("fixed"), fixed)) { if (!new_source) { - _controller.set_source_position_fixed(id, position_fixed); + control->source_fixed(id, fixed); VERBOSE2("set source position fixed: id = " << id << ", fixed = " - << A2S(position_fixed)); + << A2S(fixed)); } } else { - position_fixed = false; + fixed = false; } } else if (inner_loop == "orientation") @@ -135,35 +145,19 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) { if (!new_source) { - _controller.set_source_orientation(id, orientation); + control->source_rotation(id, orientation); VERBOSE2("set source orientation: id = " << id << ", " << orientation); } else { - // orientation is used later for _controller.new_source(...) + // orientation is used later for control->new_source(...) } } else { orientation = Orientation(); } - // source orientation_fixed is not yet implemented! - /* - if (S2A(inner_loop.get_attribute("fixed"), - orientation_fixed)) - { - if (!new_source) - { - _controller.set_source_orientation_fix(id, orientation_fixed); - VERBOSE2("set source orientation fixed"); - } - } - else - { - orientation_fixed = false; - } - */ } }//for (XMLParser:... @@ -174,7 +168,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) volume = apf::math::dB2linear(volume); if (!new_source) { - _controller.set_source_gain(id, volume); + control->source_volume(id, volume); VERBOSE2("set source volume: id = " << id << ", volume (linear) = " << volume); } @@ -189,7 +183,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) { if (!new_source) { - _controller.set_source_mute(id, muted); + control->source_mute(id, muted); VERBOSE2("set source mute mode: id = " << id << ", mute = " << A2S(muted)); } @@ -201,38 +195,31 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string name = i.get_attribute("name"); if (!name.empty() && !new_source) { - _controller.set_source_name(id,name); + control->source_name(id,name); VERBOSE2("set source name: id = " << id << ", name = " << name); } std::string properties_file = i.get_attribute("properties_file"); if (!properties_file.empty() && !new_source) { - _controller.set_source_properties_file(id,properties_file); - VERBOSE2("set source properties file name: id = " << id - << ", file = " << properties_file); + ERROR("Cannot set properties_file! This works only for new sources."); } - Source::model_t model = Source::model_t(); - if (S2A(i.get_attribute("model"),model)) + std::string model = i.get_attribute("model"); + if (!new_source && model != "") { - if (!new_source) - { - _controller.set_source_model(id,model); - VERBOSE2("set source model: id = " << id << ", model = " << model); - } + control->source_model(id,model); + VERBOSE2("set source model: id = " << id << ", model = " << model); } else { - model = Source::point; + model = "point"; } std::string port_name = i.get_attribute("port"); if (!port_name.empty() && !new_source) { - _controller.set_source_port_name(id,port_name); - VERBOSE2("set source port name: id = " << id - << ", port = " << port_name); + ERROR("Cannot set port name! This works only for new sources."); } std::string file_or_port_name = port_name; @@ -286,16 +273,15 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) "\nfile_or_port_name: " << file_or_port_name << "\nchannel: " << channel << "\nposition: " << position << - "\nposition_fixed: " << A2S(position_fixed) << "\norientation: " << orientation << - "\norientation_fixed: " << A2S(orientation_fixed) << + "\nfixed: " << A2S(fixed) << "\nvolume (linear): " << volume << "\nmuted: " << A2S(muted) << "\nproperties_file: " << properties_file << "\n"); - _controller.new_source(name, model, file_or_port_name, channel, - position, position_fixed, orientation, orientation_fixed, + control->new_source(id, name, model, file_or_port_name, channel, + position, orientation, fixed, volume, muted, properties_file); } } @@ -311,7 +297,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { - _controller.set_reference_position(Position(x,y)); + control->reference_position(Position(x,y)); VERBOSE2("set reference position: " << Position(x,y)); } else ERROR("Invalid reference position!"); @@ -321,7 +307,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { - _controller.set_reference_orientation(Orientation(azimuth)); + control->reference_rotation(Orientation(azimuth)); VERBOSE2("set reference orientation: " << Orientation(azimuth)); } else ERROR("Invalid reference orientation!"); @@ -338,7 +324,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { - _controller.set_reference_offset_position(Position(x,y)); + control->reference_offset_position(Position(x,y)); VERBOSE2("set reference offset position: " << Position(x,y)); } else ERROR("Invalid reference offset position!"); @@ -348,7 +334,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { - _controller.set_reference_offset_orientation(Orientation(azimuth)); + control->reference_offset_rotation(Orientation(azimuth)); VERBOSE2("set reference offset orientation: " << Orientation(azimuth)); } else ERROR("Invalid reference offset orientation!"); @@ -361,12 +347,18 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) { if (inner_loop == "source") { - id_t id; - if (S2A(inner_loop.get_attribute("id"),id)) + unsigned int source_number; + if (S2A(inner_loop.get_attribute("id"), source_number)) { - // if (id == 0) {} //_controller.delete_all_sources(); - // else {}//_controller.delete_source(id); - _controller.delete_source(id); + std::string id = control->get_source_id(source_number); + if (id != "") + { + control->delete_source(id); + } + else + { + ERROR("Source ID doesn't exist: " << id); + } } else ERROR("Cannot read source ID!"); } @@ -383,7 +375,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string save_scene = i.get_attribute("save"); if (save_scene != "") { - _controller.save_scene_as_XML(save_scene); + control->save_scene(save_scene); } // load scene @@ -391,7 +383,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string load_scene = i.get_attribute("load"); if (load_scene != "") { - _controller.load_scene(load_scene); + control->load_scene(load_scene); } std::string volume_str = i.get_attribute("volume"); @@ -403,7 +395,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) else if (S2A(volume_str, volume)) { // volume is given in dB in the network messages - _controller.set_master_volume(apf::math::dB2linear(volume)); + control->master_volume(apf::math::dB2linear(volume)); VERBOSE2("set master volume: " << volume << " dB"); } else ERROR("Invalid Volume Setting! (\"" << volume_str << "\")"); @@ -411,7 +403,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) bool clear_scene; if (S2A(i.get_attribute("clear"), clear_scene) && clear_scene) { - _controller.delete_all_sources(); + control->delete_all_sources(); } } else if (i == "state") @@ -421,11 +413,11 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string processing= i.get_attribute("processing"); if (processing == "start") { - _controller.start_processing(); + control->processing(true); } else if (processing == "stop") { - _controller.stop_processing(); + control->processing(false); } else if (processing != "") { @@ -437,15 +429,15 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string transport = i.get_attribute("transport"); if (transport == "start") { - _controller.transport_start(); + control->transport_start(); } else if (transport == "stop") { - _controller.transport_stop(); + control->transport_stop(); } else if (transport == "rewind") { - _controller.transport_locate(0); + control->transport_locate(0); } else if (transport != "") { @@ -463,7 +455,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) float time; if (string2time(seek, time)) { - _controller.transport_locate(time); + control->transport_locate(time); } else if (seek != "") { @@ -476,7 +468,7 @@ ssr::CommandParser::parse_cmd(const std::string& cmd) std::string tracker = i.get_attribute("tracker"); if (tracker == "reset") { - _controller.calibrate_client(); + control->calibrate_tracker(); } else if (tracker != "") { diff --git a/src/network/commandparser.h b/src/network/commandparser.h index b739099d..82571bee 100644 --- a/src/network/commandparser.h +++ b/src/network/commandparser.h @@ -36,7 +36,7 @@ namespace ssr { -struct Publisher; +namespace api { struct Publisher; } /** Parses a XML string and maps to Controller. * This class is the bridge between the network interface and the Controller. @@ -46,12 +46,12 @@ struct Publisher; class CommandParser { public: - CommandParser(Publisher& controller); + CommandParser(api::Publisher& publisher); void parse_cmd(const std::string &cmd); private: - Publisher& _controller; + api::Publisher& _publisher; }; } // namespace ssr diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 0eebdd34..946bd4c9 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -30,27 +30,19 @@ #include #include #include "connection.h" -#include "publisher.h" + /// ctor ssr::Connection::Connection(asio::io_service &io_service - , Publisher &controller, char end_of_message_character) + , api::Publisher &controller, char end_of_message_character) : _socket(io_service) , _timer(io_service) , _controller(controller) , _subscriber(*this) , _commandparser(controller) - , _is_subscribed(false) , _end_of_message_character(end_of_message_character) {} -/// dtor -ssr::Connection::~Connection() -{ - if (_is_subscribed) _controller.unsubscribe(&_subscriber); - _is_subscribed = false; -} - /** Get an instance of Connection. * @param io_service * @param controller used to (un)subscribe and get the actual Scene @@ -58,7 +50,7 @@ ssr::Connection::~Connection() **/ ssr::Connection::pointer ssr::Connection::create(asio::io_service &io_service - , Publisher& controller, char end_of_message_character) + , api::Publisher& controller, char end_of_message_character) { return pointer(new Connection(io_service, controller , end_of_message_character)); @@ -73,17 +65,11 @@ ssr::Connection::create(asio::io_service &io_service void ssr::Connection::start() { - // ok... this Connection object is activated. - // now we can connect the NetworkSubscriber. - - _controller.subscribe(&_subscriber); - _is_subscribed = true; - // this stuff should perhaps get refactored. - // need to think about this. not sure if i like this mixed into - // the Connection code. - std::string whole_scene = _controller.get_scene_as_XML(); - this->write(whole_scene); - // And we can also start_read ing. + _subs.push_back(_controller.subscribe_scene_control(&_subscriber)); + _subs.push_back(_controller.subscribe_renderer_control(&_subscriber)); + _subs.push_back(_controller.subscribe_source_metering(&_subscriber)); + _subs.push_back(_controller.subscribe_output_activity(&_subscriber)); + start_read(); // initialize the timer @@ -103,8 +89,6 @@ ssr::Connection::timeout_handler(const asio::error_code &e) { if (e) return; - _subscriber.send_levels(); - // Set timer again. _timer.expires_from_now(std::chrono::milliseconds(100)); _timer.async_wait(std::bind(&Connection::timeout_handler, shared_from_this() @@ -184,3 +168,9 @@ ssr::Connection::write_handler(std::shared_ptr str_ptr // the shared_ptr was just used to keep the string alive and // destroy it now. } + +unsigned int +ssr::Connection::get_source_number(id_t source_id) const +{ + return _controller.get_source_number(source_id); +} diff --git a/src/network/connection.h b/src/network/connection.h index 7c8aa78e..b0df85c1 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -47,7 +47,7 @@ namespace ssr { -struct Publisher; +namespace api { struct Publisher; } /// Connection class. class Connection : public std::enable_shared_from_this @@ -58,7 +58,7 @@ class Connection : public std::enable_shared_from_this typedef asio::ip::tcp::socket socket_t; static pointer create(asio::io_service &io_service - , Publisher &controller, char end_of_message_character); + , api::Publisher &controller, char end_of_message_character); void start(); void write(const std::string& writestring); @@ -66,10 +66,10 @@ class Connection : public std::enable_shared_from_this /// @return Reference to socket socket_t& socket() { return _socket; } - ~Connection(); + unsigned int get_source_number(id_t source_id) const; private: - Connection(asio::io_service &io_service, Publisher &controller + Connection(asio::io_service &io_service, api::Publisher &controller , char end_of_message_character); void start_read(); @@ -87,15 +87,15 @@ class Connection : public std::enable_shared_from_this asio::steady_timer _timer; /// Reference to Controller - Publisher &_controller; + api::Publisher &_controller; /// Subscriber obj NetworkSubscriber _subscriber; /// Commandparser obj CommandParser _commandparser; - bool _is_subscribed; - char _end_of_message_character; + + std::vector> _subs; }; } // namespace ssr diff --git a/src/network/networksubscriber.cpp b/src/network/networksubscriber.cpp index 32a23614..ff20d079 100644 --- a/src/network/networksubscriber.cpp +++ b/src/network/networksubscriber.cpp @@ -27,254 +27,136 @@ /// @file /// NetworkSubscriber class (implementation). +#include "ssr_global.h" // for ERROR() #include "networksubscriber.h" #include "apf/stringtools.h" #include "apf/math.h" // for linear2dB() #include "connection.h" +#include "legacy_position.h" // for Position +#include "legacy_orientation.h" // for Orientation using apf::str::A2S; -ssr::NetworkSubscriber::NetworkSubscriber(Connection &connection) - : _connection(connection) - , _master_level(0.0) -{} - void -ssr::NetworkSubscriber::update_all_clients(std::string str) +ssr::NetworkSubscriber::_send_message(const std::string& str) { _connection.write(str); } void -ssr::NetworkSubscriber::send_levels() +ssr::NetworkSubscriber::_send_source_message(const std::string& first_part + , id_t id, const std::string& second_part) { - source_level_map_t::iterator i; - std::string ms = ""; - for (i=_source_levels.begin(); i!=_source_levels.end(); i++) + auto source_number = _connection.get_source_number(id); + if (source_number != 0) { - ms += (""); + _send_message(first_part + A2S(source_number) + second_part); + } + else + { + ERROR("Source ID \"" << id << "\" not found"); } - ms += ""; - update_all_clients(ms); -} - -// Subscriber interface - -void -ssr::NetworkSubscriber::set_loudspeakers( - const Loudspeaker::container_t& loudspeakers) -{ - (void) loudspeakers; - //not_implemented("NetworkSubscriber::set_loudspeakers()"); } void -ssr::NetworkSubscriber::new_source(id_t id) +ssr::NetworkSubscriber::source_level(id_t id, float level) { - std::string ms = ""; - update_all_clients(ms); + _send_source_message(""); } void ssr::NetworkSubscriber::delete_source(id_t id) { - _source_levels.erase(id); - std::string ms = "" + - + ""; - update_all_clients(ms); -} - -void -ssr::NetworkSubscriber::delete_all_sources() -{ - _source_levels.clear(); - std::string ms = ""; - update_all_clients(ms); + _send_source_message( + ""); } -bool -ssr::NetworkSubscriber::set_source_position(id_t id, const Position& position) -{ - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_position_fixed(id_t id, const bool& fixed) -{ - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_orientation(id_t id - , const Orientation& orientation) -{ - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_gain(id_t id, const float& gain) -{ - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_mute(id_t id, const bool& mute) -{ - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_name(id_t id, const std::string& name) -{ - (void) id; - (void) name; -#if 0 - std::string ms = ""; - update_all_clients(ms); -#endif - return true; -} - -bool -ssr::NetworkSubscriber::set_source_properties_file(id_t id, const std::string& name) -{ - (void) id; - (void) name; -#if 0 - std::string ms = ""; - update_all_clients(ms); -#endif - return true; -} void -ssr::NetworkSubscriber::set_decay_exponent(float exponent) +ssr::NetworkSubscriber::source_position(id_t id, const Pos& pos) { - (void) exponent; - //not_implemented("NetworkSubscriber::set_decay_exponent()"); - return; + const Position position{pos}; + _send_source_message(""); } void -ssr::NetworkSubscriber::set_amplitude_reference_distance(float distance) +ssr::NetworkSubscriber::source_fixed(id_t id, bool fixed) { - (void) distance; - //not_implemented("NetworkSubscriber::set_amplitude_reference_distance()"); - return; + _send_source_message(""); } -bool -ssr::NetworkSubscriber::set_source_model(id_t id, const Source::model_t& model) -{ - std::string tmp; - tmp = A2S(model); - if (tmp == "") return false; - - std::string ms = ""; - update_all_clients(ms); - return true; -} - -bool -ssr::NetworkSubscriber::set_source_port_name(id_t id, const std::string& port_name) +void +ssr::NetworkSubscriber::source_rotation(id_t id, const Rot& rot) { - (void) id; - (void) port_name; -#if 0 - std::string ms = ""; - update_all_clients(ms); -#endif - return true; + const Orientation orientation{rot}; + _send_source_message(""); } -bool -ssr::NetworkSubscriber::set_source_file_name(id_t id, const std::string& file_name) +void +ssr::NetworkSubscriber::source_volume(id_t id, float gain) { - (void) id; - (void) file_name; - return 1; + _send_source_message(""); } -bool -ssr::NetworkSubscriber::set_source_file_channel(id_t id - , const int& file_channel) +void +ssr::NetworkSubscriber::source_mute(id_t id, bool mute) { - (void) id; - (void) file_channel; - return 1; + _send_source_message(""); } -bool -ssr::NetworkSubscriber::set_source_file_length(id_t id, const long int& length) +void +ssr::NetworkSubscriber::source_model(id_t id, const std::string& model) { - std::string ms = ""; - update_all_clients(ms); - return true; + _send_source_message(""); } void -ssr::NetworkSubscriber::set_reference_position(const Position& position) +ssr::NetworkSubscriber::reference_position(const Pos& pos) { - std::string ms = ""; - update_all_clients(ms); + const Position position{pos}; + _send_message(""); } void -ssr::NetworkSubscriber::set_reference_orientation(const Orientation& orientation) +ssr::NetworkSubscriber::reference_rotation(const Rot& rot) { - std::string ms = ""; - update_all_clients(ms); + const Orientation orientation{rot}; + _send_message(""); } void -ssr::NetworkSubscriber::set_reference_offset_position(const Position& position) +ssr::NetworkSubscriber::reference_offset_position(const Pos& pos) { - std::string ms = ""; - update_all_clients(ms); + const Position position{pos}; + _send_message(""); } void -ssr::NetworkSubscriber::set_reference_offset_orientation(const Orientation& orientation) +ssr::NetworkSubscriber::reference_offset_rotation(const Rot& rot) { - std::string ms = ""; - update_all_clients(ms); + const Orientation orientation{rot}; + _send_message(""); } void -ssr::NetworkSubscriber::set_master_volume(float volume) +ssr::NetworkSubscriber::master_volume(float volume) { - std::string ms = ""; - update_all_clients(ms); + _send_message(""); } void -ssr::NetworkSubscriber::set_source_output_levels(id_t id, float* first - , float* last) +ssr::NetworkSubscriber::output_activity(id_t id, float* first, float* last) { std::string ms = ""; - update_all_clients(ms); -} - -void -ssr::NetworkSubscriber::set_processing_state(bool state) -{ - (void) state; -} - -void -ssr::NetworkSubscriber::set_transport_state( - const std::pair& state) -{ - // temporary hack: only start/stop is forwarded, the "time" in samples is - // ignored - static bool previous_state = false; - if (previous_state != state.first) - { - std::string ms = ""; - update_all_clients(ms); - previous_state = state.first; - } -} - -void -ssr::NetworkSubscriber::set_auto_rotation(bool auto_rotate_sources) -{ - (void) auto_rotate_sources; -} - -void -ssr::NetworkSubscriber::set_cpu_load(float load) -{ - (void)load; - // temporarily disabled: - /* - std::string ms = ""; - update_all_clients(ms); - */ -} - -void -ssr::NetworkSubscriber::set_sample_rate(int sr) -{ - (void)sr; -} - -void -ssr::NetworkSubscriber::set_master_signal_level(float level) -{ - _master_level = level; - //std::string ms = ""; - //update_all_clients(ms); -} - -bool -ssr::NetworkSubscriber::set_source_signal_level(const id_t id, const float& level) -{ - _source_levels[id] = level; - std::string ms = ""; - //update_all_clients(ms); - return true; + _send_message(ms); } diff --git a/src/network/networksubscriber.h b/src/network/networksubscriber.h index af5eb452..b387f12f 100644 --- a/src/network/networksubscriber.h +++ b/src/network/networksubscriber.h @@ -30,7 +30,7 @@ #ifndef SSR_NETWORKSUBSCRIBER_H #define SSR_NETWORKSUBSCRIBER_H -#include "subscriber.h" +#include "api.h" #include namespace ssr @@ -43,59 +43,56 @@ class Connection; * strings (XML-messages in ASDF format) and sends it over a Connection to * the connected client. **/ -class NetworkSubscriber : public Subscriber +class NetworkSubscriber : public api::SceneControlEvents + , public api::RendererControlEvents + , public api::SourceMetering + , public api::OutputActivity { public: - NetworkSubscriber(Connection &connection); - - // XXX: This is just to make the old code work. - // only sends string to one connection. - void update_all_clients(std::string str); - void send_levels(); - - // Subscriber Interface - virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers); - virtual void new_source(id_t id); - virtual void delete_source(id_t id); - virtual void delete_all_sources(); - virtual bool set_source_position(id_t id, const Position& position); - virtual bool set_source_position_fixed(id_t id, const bool& fix); - virtual bool set_source_orientation(id_t id, const Orientation& orientation); - virtual bool set_source_gain(id_t id, const float& gain); - virtual bool set_source_mute(id_t id, const bool& mute); - virtual bool set_source_name(id_t id, const std::string& name); - virtual bool set_source_properties_file(id_t id, const std::string& name); - virtual bool set_source_model(id_t id, const Source::model_t& model); - virtual bool set_source_port_name(id_t id, const std::string& port_name); - virtual bool set_source_file_name(id_t id, const std::string& file_name); - virtual bool set_source_file_channel(id_t id, const int& file_channel); - virtual bool set_source_file_length(id_t id, const long int& length); - virtual void set_reference_position(const Position& position); - virtual void set_reference_orientation(const Orientation& orientation); - virtual void set_reference_offset_position(const Position& position); - virtual void set_reference_offset_orientation(const Orientation& orientation); - virtual void set_master_volume(float volume); - - virtual void set_source_output_levels(id_t id, float* first, float* last); - virtual void set_processing_state(bool state); - //virtual void set_transport_state(JackClient::State state); - virtual void set_transport_state( - const std::pair& state); - - virtual void set_auto_rotation(bool auto_rotate_sources); - virtual void set_decay_exponent(float exponent); - virtual void set_amplitude_reference_distance(float distance); - virtual void set_master_signal_level(float level); - virtual void set_cpu_load(float load); - virtual void set_sample_rate(int sample_rate); - virtual bool set_source_signal_level(const id_t id, const float& level); + explicit NetworkSubscriber(Connection &connection) + : _connection(connection) + {} private: - Connection &_connection; - typedef std::map source_level_map_t; - source_level_map_t _source_levels; - float _master_level; + // SceneControlEvents + + void auto_rotate_sources(bool) override {} + void delete_source(id_t id) override; + void source_position(id_t id, const Pos& position) override; + void source_rotation(id_t id, const Rot& rotation) override; + void source_volume(id_t id, float gain) override; + void source_mute(id_t id, bool mute) override; + void source_name(id_t, const std::string&) override {} + void source_model(id_t id, const std::string& model) override; + void source_fixed(id_t id, bool fix) override; + + void reference_position(const Pos& position) override; + void reference_rotation(const Rot& rotation) override; + + void master_volume(float volume) override; + void decay_exponent(float) override {} + void amplitude_reference_distance(float) override {} + + // RendererControlEvents + + void processing(bool) override {} + void reference_offset_position(const Pos& position) override; + void reference_offset_rotation(const Rot& rotation) override; + + // SourceMetering + + void source_level(id_t id, float level) override; + + // OutputActivity + + void output_activity(id_t id, float* first, float* last) override; + + void _send_message(const std::string& str); + void _send_source_message( + const std::string& first_part, id_t id, const std::string& second_part); + + Connection &_connection; }; } // namespace ssr diff --git a/src/network/server.cpp b/src/network/server.cpp index f3e3630a..4ffe975e 100644 --- a/src/network/server.cpp +++ b/src/network/server.cpp @@ -29,10 +29,14 @@ #include #include "server.h" +#include "ssr_global.h" // for VERBOSE2() +#include "legacy_xmlsceneprovider.h" // for LegacyXmlSceneProvider -ssr::Server::Server(Publisher& controller, int port - , char end_of_message_character) +ssr::Server::Server(api::Publisher& controller + , LegacyXmlSceneProvider& scene_provider + , int port, char end_of_message_character) : _controller(controller) + , _scene_provider(scene_provider) , _io_service() , _acceptor(_io_service , asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) @@ -62,6 +66,8 @@ ssr::Server::handle_accept(Connection::pointer new_connection { if (!error) { + // A hack to mimic the old behavior of the legacy network interface: + new_connection->write(_scene_provider.get_scene_as_XML()); new_connection->start(); start_accept(); } diff --git a/src/network/server.h b/src/network/server.h index 0e2338c9..804abc3b 100644 --- a/src/network/server.h +++ b/src/network/server.h @@ -46,13 +46,15 @@ namespace ssr { -struct Publisher; +namespace api { struct Publisher; } +struct LegacyXmlSceneProvider; /// Server class. class Server { public: - Server(Publisher& controller, int port, char end_of_message_character); + Server(api::Publisher& controller, LegacyXmlSceneProvider& scene_provider + , int port, char end_of_message_character); ~Server(); void start(); void stop(); @@ -63,7 +65,9 @@ class Server void handle_accept(Connection::pointer new_connection , const asio::error_code &error); - Publisher& _controller; + api::Publisher& _controller; + // Just a hack for get_scene_as_XML(): + LegacyXmlSceneProvider& _scene_provider; asio::io_service _io_service; asio::ip::tcp::acceptor _acceptor; std::thread *_network_thread; diff --git a/src/publisher.h b/src/publisher.h deleted file mode 100644 index f8f912c5..00000000 --- a/src/publisher.h +++ /dev/null @@ -1,170 +0,0 @@ -/****************************************************************************** - * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * - * Copyright © 2006-2012 Quality & Usability Lab, * - * Telekom Innovation Laboratories, TU Berlin * - * * - * This file is part of the SoundScape Renderer (SSR). * - * * - * The SSR is free software: you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free * - * Software Foundation, either version 3 of the License, or (at your option) * - * any later version. * - * * - * The SSR 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 General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along * - * with this program. If not, see . * - * * - * The SSR is a tool for real-time spatial audio reproduction providing a * - * variety of rendering algorithms. * - * * - * http://spatialaudio.net/ssr ssr@spatialaudio.net * - ******************************************************************************/ - -/// @file -/// Abstract %Publisher (definition). - -// TODO: use a less general name? -#ifndef SSR_PUBLISHER_H -#define SSR_PUBLISHER_H - -#include // for uint32_t - -#include "source.h" -#include "ssr_global.h" - -using jack_nframes_t = uint32_t; // from - -namespace ssr -{ - -struct Subscriber; - -/** Abstract interface to controller class (and similar classes). - * All member functions are public, everyone can call them. - * @attention For now, none of the member functions has a return value. That is - * because if we are connected via a slow IP interface we do not want to wait - * for transferring back the return value. - * \par - * Anyhow, maybe this is not a good decision. Maybe there should be return - * values? - **/ -struct Publisher -{ - virtual ~Publisher() {} ///< dtor. does nothing - - /// load scene from XML file. - /// @param scene_file_name name of XML file to load. - virtual bool load_scene(const std::string& scene_file_name) = 0; - virtual bool save_scene_as_XML(const std::string& filename) const = 0; - - virtual void start_processing() = 0; ///< start processing - virtual void stop_processing() = 0; ///< stop processing - - /// create new source. - /// @param name name of the new source - /// @param model model (=type) of the new source - /// @param file_or_port_name name of audio file or JACK port. If a JACK port - /// is given, @a channel has to be zero! - /// @param channel number of channel in audio file. If zero, a JACK port is - /// given instead of an audio file. - /// @param position initial position of the new source. - /// @param orientation initial orientation of the new source. - /// @param gain initial gain of the new source. - virtual void new_source(const std::string& name, Source::model_t model, - const std::string& file_or_port_name, int channel = 0, - const Position& position = Position(), const bool pos_fix = false, - const Orientation& orientation = Orientation(), const bool or_fix = false, - const float gain = 1.0f, const bool mute_state = false, - const std::string& properties_file = "") = 0; - - /// delete a source - /// @param[in] id id of the source to be deleted - virtual void delete_source(id_t id) = 0; - - /// delete all sources - virtual void delete_all_sources() = 0; - - /// set position of a source - /// @param id source id. - /// @param position source position. - /// @todo include some sort of identification who sent the request. - virtual void set_source_position(id_t id, const Position& position) = 0; - /// set orientation of a source - virtual void set_source_orientation(id_t id, - const Orientation& orientation) = 0; - /// set gain (=volume) of a source - virtual void set_source_gain(id_t id, float gain) = 0; - /// mute/unmute source - virtual void set_source_mute(id_t id, bool mute) = 0; - /// instantaneous level of audio stream - virtual void set_source_signal_level(const id_t id, - const float level) = 0; - /// set name of a source - virtual void set_source_name(id_t id, const std::string& name) = 0; - virtual void set_source_properties_file(id_t id, const std::string& name) = 0; - /// set model (=type) of a source - virtual void set_source_model(id_t id, Source::model_t model) = 0; - /// set JACK port of a source - virtual void - set_source_port_name(id_t id, const std::string& port_name) = 0; - virtual void - set_source_position_fixed(id_t id, const bool fix) = 0; - - /// set position of the reference - /// @param position well, the position - virtual void set_reference_position(const Position& position) = 0; - /// set orientation of the reference - virtual void set_reference_orientation(const Orientation& orientation) = 0; - virtual void set_reference_offset_position(const Position& position) = 0; - virtual void set_reference_offset_orientation(const Orientation& orientation) = 0; - - /// set master volume of the whole scene - virtual void set_master_volume(float volume) = 0; - - /// set amplitude decay exponent - virtual void - set_decay_exponent(float exponent) = 0; - - /// set amplitude reference distance - virtual void - set_amplitude_reference_distance(float distance) = 0; - - /// set instantaneous overall audio level (linear scale) - virtual void set_master_signal_level(float level) = 0; - - /// set CPU load in percent as estimated by JACK - virtual void set_cpu_load(const float load) = 0; - - /// sets the sample rate in all subscribers - virtual void publish_sample_rate(const int sample_rate) = 0; - - /// returns what type the current renderer actually is - virtual std::string get_renderer_name() const = 0; - - /// show head in GUI? - virtual bool show_head() const = 0; - - virtual void transport_start() = 0; ///< start JACK transport - - virtual void transport_stop() = 0; ///< stop JACK transport - /// set JACK transport location - virtual bool transport_locate(float time_in_sec) = 0; - /// This is temporarily used to calibrate the tracker - virtual void calibrate_client() = 0; - - virtual void set_processing_state(bool state) = 0; - - virtual void set_auto_rotation(bool auto_rotate_sources) = 0; - - virtual void subscribe(Subscriber* subscriber) = 0; - virtual void unsubscribe(Subscriber* subscriber) = 0; - virtual std::string get_scene_as_XML() const = 0; -}; - -} // namespace ssr - -#endif diff --git a/src/rendererbase.h b/src/rendererbase.h index fb29cfe8..f05466e1 100644 --- a/src/rendererbase.h +++ b/src/rendererbase.h @@ -39,10 +39,8 @@ #include "apf/parameter_map.h" #include "apf/math.h" // for dB2linear() -// TODO: avoid multiple ambiguous "Source" classes -#include "source.h" // for ::Source::model_t - #include "maptools.h" +#include "legacy_directionalpoint.h" // for Position etc. #ifndef SSR_QUERY_POLICY #define SSR_QUERY_POLICY apf::disable_queries @@ -52,8 +50,6 @@ namespace ssr { /** Renderer base class. - * @todo more documentation! - * * The parallel rendering engine uses the non-blocking datastructure RtList to * communicate between realtime and non-realtime threads. * All non-realtime accesses to RtList%s have to be locked with @@ -203,11 +199,12 @@ class RendererBase : public apf::MimoProcessor - void get_loudspeakers(SomeListType&) {} + void get_loudspeakers(SomeListType&) const {} - std::unique_ptr get_scoped_lock() - { - // TODO: in C++14, use make_unique() - return std::unique_ptr(new ScopedLock(_lock)); - } + auto get_scoped_lock() { return std::make_unique(_lock); } const sample_type master_volume_correction; // linear @@ -245,11 +238,9 @@ class RendererBase : public apf::MimoProcessor> _source_map; - std::map _source_map; - - int _highest_id; + size_t _next_id_suffix = 0; typename _base::Lock _lock; }; @@ -266,7 +257,6 @@ RendererBase::RendererBase(const apf::parameter_map& p) , _master_level() , _source_list(_fifo) , _show_head(true) - , _highest_id(0) {} /** Create a new source. @@ -274,13 +264,26 @@ RendererBase::RendererBase(const apf::parameter_map& p) * @throw unknown whatever the Derived::Source constructor throws **/ template -int RendererBase::add_source(const apf::parameter_map& p) +std::string +RendererBase::add_source(id_t requested_id + , const apf::parameter_map& p) { - int id = _get_new_id(); + std::string id = requested_id; + if (id.size() && _source_map.find(id) != _source_map.end()) + { + throw std::runtime_error("Source with ID \"" + id + "\" already exists"); + } + if (id == "") + { + do + { + id = ".ssr:" + apf::str::A2S(_next_id_suffix++); + } + while (_source_map.find(id) != _source_map.end()); + } typename Derived::Input::Params in_params; in_params = p; - in_params.set("id", in_params.get("id", id)); auto in = this->add(in_params); // WARNING: if Derived::Input throws an exception, the SSR crashes! @@ -290,8 +293,6 @@ int RendererBase::add_source(const apf::parameter_map& p) src_params.parent = &this->derived(); src_params.fifo = &_fifo; src_params.input = in; - - // For now, Input ID and Source ID are the same: src_params.id = id; typename Derived::Source* src; @@ -314,11 +315,10 @@ int RendererBase::add_source(const apf::parameter_map& p) _source_map[id] = src; return id; - // TODO: what happens on failure? can there be failure? } template -void RendererBase::rem_source(int id) +void RendererBase::rem_source(id_t id) { auto delinquent = _source_map.find(id); @@ -351,21 +351,22 @@ void RendererBase::rem_all_sources() { this->rem_source(_source_map.begin()->first); } - _highest_id = 0; } -template -typename RendererBase::Source* -RendererBase::get_source(int id) -{ - return maptools::get_item(_source_map, id); -} template -int -RendererBase::_get_new_id() +typename RendererBase::Source* +RendererBase::get_source(id_t id) { - return ++_highest_id; + auto iter = _source_map.find(id); + if (iter == _source_map.end()) + { + return nullptr; + } + else + { + return iter->second; + } } /// A sound source. @@ -389,7 +390,7 @@ class RendererBase::Source Derived* parent = nullptr; const typename Derived::Input* input = nullptr; apf::CommandQueue* fifo = nullptr; - int id = 0; + std::string id; using apf::parameter_map::operator=; }; @@ -402,7 +403,7 @@ class RendererBase::Source , orientation(*p.fifo) , gain(*p.fifo, sample_type(1.0)) , mute(*p.fifo, false) - , model(*p.fifo, ::Source::point) + , model(*p.fifo, "point") , weighting_factor() , id(p.id) , _input(*(p.input ? p.input : throw std::logic_error( @@ -430,11 +431,11 @@ class RendererBase::Source apf::SharedData orientation; apf::SharedData gain; apf::SharedData mute; - apf::SharedData< ::Source::model_t> model; + apf::SharedData model; apf::BlockParameter weighting_factor; - const int id; + const std::string id; protected: const typename Derived::Input& _input; @@ -476,7 +477,7 @@ void RendererBase::Source::_process() if (std::strcmp(this->parent.name(), "BrsRenderer") != 0 && std::strcmp(this->parent.name(), "GenericRenderer") != 0) { - if (this->model != ::Source::plane) + if (this->model != "plane") { float source_distance = (this->position - (_input.parent.state.reference_position diff --git a/src/rendersubscriber.h b/src/rendersubscriber.h index e10eac06..5f15aab0 100644 --- a/src/rendersubscriber.h +++ b/src/rendersubscriber.h @@ -27,239 +27,197 @@ /// @file /// %RenderSubscriber (definition). -// TODO: the whole publish/subscribe-thing should be redesigned, so maybe the -// RenderSubscriber will be replaced by something else ... -// Also, the actual renderer supports only a subset of the current (r1509) -// Subscriber interface (no audio port names, no file names, ...). - #ifndef SSR_RENDERSUBSCRIBER_H #define SSR_RENDERSUBSCRIBER_H -#include "subscriber.h" -#include +#include "api.h" +#include "legacy_position.h" // legacy Position type +#include "legacy_orientation.h" // legacy Orientation type +#include "ssr_global.h" // for WARNING() + +#include "rendererbase.h" // for Source namespace ssr { template -class RenderSubscriber : public Subscriber +class RenderSubscriber : public api::BundleEvents + // TODO: new_source + , public api::SceneControlEvents + , public api::RendererControlEvents + , public api::RendererInformationEvents { - public: - RenderSubscriber(Renderer &renderer) : _renderer(renderer) {} - - // Subscriber Interface - virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers); - - virtual void new_source(id_t id) { (void) id; } - - virtual void delete_source(id_t id) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.rem_source(id); - } - - virtual void delete_all_sources() - { - auto guard = _renderer.get_scoped_lock(); - _renderer.rem_all_sources(); - } - - virtual bool set_source_position(id_t id, const Position& position) - { - auto guard = _renderer.get_scoped_lock(); - auto src = _renderer.get_source(id); - if (!src) return false; - src->derived().position = position; - return true; - } - - virtual bool set_source_orientation(id_t id, const Orientation& orientation) - { - auto guard = _renderer.get_scoped_lock(); - auto src = _renderer.get_source(id); - if (!src) return false; - src->derived().orientation = orientation; - return true; - } - - virtual bool set_source_gain(id_t id, const float& gain) - { - auto guard = _renderer.get_scoped_lock(); - auto src = _renderer.get_source(id); - if (!src) return false; - src->derived().gain = gain; - return true; - } - - virtual bool set_source_mute(id_t id, const bool& mute) - { - auto guard = _renderer.get_scoped_lock(); - auto src = _renderer.get_source(id); - if (!src) return false; - src->derived().mute = mute; - return true; - } - - virtual bool set_source_name(id_t id, const std::string& name) - { - (void) id; - (void) name; - return true; - } - - virtual bool set_source_brir_file_name(id_t id, const std::string& name) - { - (void) id; - (void) name; - return true; - } - - virtual bool set_source_model(id_t id, const Source::model_t& model) - { - auto guard = _renderer.get_scoped_lock(); - auto src = _renderer.get_source(id); - if (!src) return false; - src->derived().model = model; - return true; - } - - virtual bool set_source_port_name(id_t id, const std::string& port_name) - { - (void) id; - (void) port_name; - return true; - } - - virtual bool set_source_file_name(id_t id, const std::string& file_name) - { - (void) id; - (void) file_name; - return 1; - } - - virtual bool set_source_file_channel(id_t id, const int& file_channel) - { - (void) id; - (void) file_channel; - return 1; - } - - virtual bool set_source_file_length(id_t id, const long int& length) - { - (void) id; - (void) length; - return true; - } - - virtual void set_reference_position(const Position& position) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.reference_position = position; - } - - virtual void set_reference_orientation(const Orientation& orientation) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.reference_orientation = orientation; - } - - virtual void set_reference_offset_position(const Position& position) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.reference_offset_position = position; - } - - virtual void set_reference_offset_orientation(const Orientation& orientation) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.reference_offset_orientation = orientation; - } - - virtual void set_master_volume(float volume) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.master_volume = volume; - } - - virtual void set_source_output_levels(id_t, float*, float*) {} - - virtual void set_processing_state(bool state) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.processing = state; - } - - virtual void set_transport_state( - const std::pair& state) - { - (void) state; - } - - virtual void set_auto_rotation(bool auto_rotate_sources) - { - (void) auto_rotate_sources; - } - - virtual void set_decay_exponent(float exponent) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.decay_exponent = exponent; - } - - virtual void set_amplitude_reference_distance(float distance) - { - auto guard = _renderer.get_scoped_lock(); - _renderer.state.amplitude_reference_distance = distance; - } - - virtual void set_master_signal_level(float level) - { - (void) level; - } - - virtual void set_cpu_load(float load) - { - (void) load; - } - - virtual void set_sample_rate(int sample_rate) - { - (void) sample_rate; - } - - virtual bool set_source_signal_level(const id_t id, const float& level) - { - (void) id; - (void) level; - return true; - } - - virtual bool set_source_properties_file(ssr::id_t, const std::string&) - { - return 1; - } - - virtual bool set_source_position_fixed(ssr::id_t id, const bool& fix) - { - (void) id; - (void) fix; - return true; - } - - private: - Renderer& _renderer; +public: + RenderSubscriber(Renderer &renderer) : _renderer(renderer) {} + + void get_data(api::RendererControlEvents* subscriber) + { + subscriber->processing(_processing); + subscriber->reference_offset_position(_reference_offset_position); + subscriber->reference_offset_rotation(_reference_offset_rotation); + } + + void get_data(api::RendererInformationEvents* subscriber) + { + subscriber->renderer_name(_renderer_name); + subscriber->sample_rate(_sample_rate); + subscriber->loudspeakers(_loudspeakers); + } + +private: + using Source = typename RendererBase::Source; + + template + void _set_source_member(id_t id, PTM member, T&& arg) + { + auto* src = _renderer.get_source(id); + if (!src) + { + WARNING("Source \"" << id << "\" does not exist."); + } + else + { + src->*member = std::forward(arg); + } + } + + // BundleEvents + + void bundle_start() override + { + // TODO: implement + } + + void bundle_stop() override + { + // TODO: implement + } + + // SceneControlEvents + + void auto_rotate_sources(bool auto_rotate) override + { + (void) auto_rotate; + } + + void delete_source(id_t id) override + { + _renderer.rem_source(id); + } + + void source_position(id_t id, const Pos& pos) override + { + _set_source_member(id, &Source::position, pos); + } + + void source_rotation(id_t id, const Rot& rot) override + { + _set_source_member(id, &Source::orientation, rot); + } + + void source_volume(id_t id, float volume) override + { + _set_source_member(id, &Source::gain, volume); + } + + void source_mute(id_t id, bool mute) override + { + _set_source_member(id, &Source::mute, mute); + } + + void source_name(id_t, const std::string&) override + { + // Not used in renderer + } + + void source_model(id_t id, const std::string& model) override + { + _set_source_member(id, &Source::model, model); + } + + void source_fixed(id_t, bool) override + { + // Not used in renderer + } + + void reference_position(const Pos& pos) override + { + Position position{pos}; + _renderer.state.reference_position = position; + } + + void reference_rotation(const Rot& rot) override + { + _renderer.state.reference_orientation = rot; + } + + void master_volume(float volume) override + { + _renderer.state.master_volume = volume; + } + + void decay_exponent(float exponent) override + { + _renderer.state.decay_exponent = exponent; + } + + void amplitude_reference_distance(float distance) override + { + _renderer.state.amplitude_reference_distance = distance; + } + + // RendererControlEvents + + void processing(bool state) override + { + _renderer.state.processing = state; + _processing = state; + } + + void reference_offset_position(const Pos& pos) override + { + Position position{pos}; + _renderer.state.reference_offset_position = position; + _reference_offset_position = pos; + } + + void reference_offset_rotation(const Rot& rot) override + { + Orientation orientation{rot}; + // For backwards compatibility, 90 degrees are added when converting to + // Orientation. This, however, should not be done for the reference offset. + orientation.azimuth -= 90.0f; + _renderer.state.reference_offset_orientation = orientation; + _reference_offset_rotation = rot; + } + + // RendererInformationEvents (not needed in renderer!) + + void renderer_name(const std::string& name) override + { + _renderer_name = name; + } + + void sample_rate(int rate) override + { + _sample_rate = rate; + } + + void loudspeakers(const std::vector& loudspeakers) override + { + _loudspeakers = loudspeakers; + } + + Renderer& _renderer; + + bool _processing{false}; + Pos _reference_offset_position; + Rot _reference_offset_rotation; + std::string _renderer_name; + int _sample_rate; + std::vector _loudspeakers; }; -template -void -RenderSubscriber::set_loudspeakers( - const Loudspeaker::container_t& loudspeakers) -{ - (void)loudspeakers; - - // TODO: handle loudspeakers differently. Maybe remove them from the Scene? -} - } // namespace ssr #endif diff --git a/src/scene.cpp b/src/scene.cpp deleted file mode 100644 index bf17bd4d..00000000 --- a/src/scene.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/****************************************************************************** - * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * - * Copyright © 2006-2012 Quality & Usability Lab, * - * Telekom Innovation Laboratories, TU Berlin * - * * - * This file is part of the SoundScape Renderer (SSR). * - * * - * The SSR is free software: you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free * - * Software Foundation, either version 3 of the License, or (at your option) * - * any later version. * - * * - * The SSR 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 General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along * - * with this program. If not, see . * - * * - * The SSR is a tool for real-time spatial audio reproduction providing a * - * variety of rendering algorithms. * - * * - * http://spatialaudio.net/ssr ssr@spatialaudio.net * - ******************************************************************************/ - -/// @file -/// %Scene class (implementation). - -#include - -#include "scene.h" -#include "source.h" - -ssr::Scene::Scene() : - // reference looking straight ahead (in positive y-direction) - _reference(Position(0, 0), Orientation(90)), - _reference_offset(Position(0, 0), Orientation(0)), - _master_volume(1.0f), - _decay_exponent(1.0f), - _amplitude_reference_distance(3.0f), - _master_signal_level(0.0f), - _cpu_load(0.0f), _sample_rate(0u), - _processing_state(true), - _auto_rotate_sources(true) -{} - -ssr::Scene::~Scene() -{ - //maptools::purge(_source_map); -} - -ssr::Scene::loudspeakers_t::size_type ssr::Scene::get_number_of_loudspeakers() const -{ - return _loudspeakers.size(); -} - -void ssr::Scene::set_loudspeakers(const Loudspeaker::container_t& loudspeakers) -{ - // mind the difference between _loudspeakers and loudspeakers! - if (_loudspeakers.size() != 0) - { - ERROR("BUG: Loudspeaker list can only be set once for a Scene!"); - _loudspeakers.clear(); - } - // reserve memory to avoid unnecessary re-allocation - _loudspeakers.reserve(loudspeakers.size()); - for (const auto& ls: loudspeakers) - { - // construction of temporary variable with type conversion ctor - // and copy construction into vector - _loudspeakers.push_back(Loudspeaker(ls)); - } -} - -void ssr::Scene::new_source(const id_t id) -{ - // do nothing if id already exists: - if (maptools::get_item(_source_map, id) == nullptr) - { - VERBOSE("Adding source " << id << " to source map!"); - _source_map[id] = Source(_loudspeakers.size()); - } -} - -void ssr::Scene::delete_source(id_t id) -{ - // this should call the destructor for the Source object. - // IMPORTANT: the map holds the Sources directly, no pointers! - _source_map.erase(id); -} - -void ssr::Scene::delete_all_sources() -{ - // this should call the destructor for all Source objects. - // IMPORTANT: the map holds the Sources directly, no pointers! - _source_map.clear(); -} - -bool ssr::Scene::set_source_position(id_t id, const Position& position) -{ - return _set_source_member(id, &Source::position, position); -} - -bool ssr::Scene::set_source_orientation(id_t id, const Orientation& orientation) -{ - return _set_source_member(id, &Source::orientation, orientation); -} - -bool ssr::Scene::set_source_gain(id_t id, const float& gain) -{ - return _set_source_member(id, &Source::gain, gain); -} - -bool ssr::Scene::set_source_signal_level(id_t id, const float& level) -{ - return _set_source_member(id, &Source::signal_level, level); -} - - -bool ssr::Scene::set_source_mute(id_t id, const bool& mute) -{ - return _set_source_member(id, &Source::mute, mute); -} - -bool ssr::Scene::set_source_name(id_t id, const std::string& name) -{ - return _set_source_member(id, &Source::name, name); -} - -bool ssr::Scene::set_source_properties_file(id_t id, const std::string& name) -{ - return _set_source_member(id, &Source::properties_file, name); -} - -bool ssr::Scene::set_source_port_name(id_t id, const std::string& port_name) -{ - return _set_source_member(id, &Source::port_name, port_name); -} - -bool ssr::Scene::set_source_file_name(id_t id, const std::string& file_name) -{ - return _set_source_member(id, &Source::audio_file_name, file_name); -} - -bool ssr::Scene::set_source_file_channel(id_t id, const int& file_channel) -{ - return _set_source_member(id, &Source::audio_file_channel, file_channel); -} - -bool ssr::Scene::set_source_position_fixed(id_t id, const bool& fixed) -{ - return _set_source_member(id, &Source::fixed_position, fixed); -} - -bool ssr::Scene::set_source_file_length(id_t id, const long int& length) -{ - return _set_source_member(id, &Source::file_length, length); -} - -void ssr::Scene::set_reference_position(const Position& position) -{ - _reference.position = position; -} - -void ssr::Scene::set_reference_orientation(const Orientation& orientation) -{ - _reference.orientation = orientation; -} - -void ssr::Scene::set_reference_offset_position(const Position& position) -{ - _reference_offset.position = position; -} - -void ssr::Scene::set_reference_offset_orientation(const Orientation& orientation) -{ - _reference_offset.orientation = orientation; -} - -bool ssr::Scene::set_source_model(id_t id, const Source::model_t& model) -{ - return _set_source_member(id, &Source::model, model); -} - -// linear volume! -void ssr::Scene::set_master_volume(float volume) -{ - _master_volume = volume; -} - -void ssr::Scene::set_decay_exponent(float exponent) -{ - _decay_exponent = exponent; -} -void ssr::Scene::set_amplitude_reference_distance(float dist) -{ - _amplitude_reference_distance = dist; -} - -// linear scale! -void ssr::Scene::set_master_signal_level(float level) -{ - _master_signal_level = level; -} - -void ssr::Scene::set_cpu_load(float load) -{ - _cpu_load = load; -} - -void ssr::Scene::set_sample_rate(int sr) -{ - _sample_rate = sr; -} - -void ssr::Scene::set_source_output_levels(id_t id, float* first, float* last) -{ - Source* const source_ptr = maptools::get_item(_source_map, id); - - if (!source_ptr) - { - ERROR("Source " << id << " doesn't exist!"); - return; - } - - if (source_ptr->output_levels.size() == size_t(std::distance(first,last))) - { - std::copy(first, last, source_ptr->output_levels.begin()); - } -} - -void ssr::Scene::set_processing_state(bool state) -{ - _processing_state = state; -} - -/// _. @return processing state -bool ssr::Scene::get_processing_state() const -{ - return _processing_state; -} - -/// _. @return master volume -float ssr::Scene::get_master_volume() const -{ - return _master_volume; -} - -/// _. @return amplitude decay exponent -float ssr::Scene::get_decay_exponent() const -{ - return _decay_exponent; -} - -/// _. @return amplitude reference distance -float ssr::Scene::get_amplitude_reference_distance() const -{ - return _amplitude_reference_distance; -} - -/// _. @return instantaneous overall audio signal level -float ssr::Scene::get_master_signal_level() const -{ - return _master_signal_level; -} - -/// _. @return CPU load in percent -float ssr::Scene::get_cpu_load() const -{ - return _cpu_load; -} - -/// _. @return current sample rate -int ssr::Scene::get_sample_rate() const -{ - return _sample_rate; -} - - -//void ssr::Scene::set_transport_state(JackClient::State state) -void ssr::Scene::set_transport_state(const std::pair& state) -{ - //_transport_state = state; - _transport_playing = state.first; - _transport_position = state.second; -} - -void ssr::Scene::set_auto_rotation(bool auto_rotate_sources) -{ - _auto_rotate_sources = auto_rotate_sources; -} - -bool ssr::Scene::is_playing() const -{ - //return _transport_state.playing; - return _transport_playing; -} - -ssr::jack_nframes_t ssr::Scene::get_transport_position() const -{ - //return _transport_state.position; - return _transport_position; -} - -bool ssr::Scene::get_auto_rotation() const -{ - return _auto_rotate_sources; -} - -Source ssr::Scene::get_source(id_t id) const -{ - auto source = Source(_loudspeakers.size()); - auto source_ptr = maptools::get_item(_source_map, id); - - if (!source_ptr) ERROR("Source " << id << " doesn't exist!"); - else source = *source_ptr; - - return source; -} - -/** _. - * @param id ID of the source - * @return position of the source - * @warning If @a id is not found, a unique_ptr to NULL is returned! - **/ -std::unique_ptr ssr::Scene::get_source_position(id_t id) const -{ - std::unique_ptr position(new Position); // standard ctor - if (_get_source_member(id, &Source::position, *position)) return position; - position.reset(); // set to NULL - return position; -} - -/** _. - * @param id ID of the source - * @return position of the source - * @warning If @a id is not found, a unique_ptr to NULL is returned! - **/ -std::unique_ptr ssr::Scene::get_source_orientation(id_t id) const -{ - std::unique_ptr orientation(new Orientation); // standard ctor - if (_get_source_member(id, &Source::orientation, *orientation)) return orientation; - orientation.reset(); // set to NULL - return orientation; -} - -/** _. - * @param id ID of the source - * @return source model - * @warning If @a id is not found, Source::unknown is returned - **/ -Source::model_t ssr::Scene::get_source_model(id_t id) const -{ - Source::model_t model; - if (_get_source_member(id, &Source::model, model)) return model; - return Source::unknown; -} - -/** _. - * @param id ID of the source - * @return linear source gain (=volume) - * @warning If @a id is not found, 0 is returned - **/ -float ssr::Scene::get_source_gain(id_t id) const -{ - float gain; - if (_get_source_member(id, &Source::gain, gain)) return gain; - return 0; -} - -/** _. - * @param id ID of the source - * @return mute state - * @warning If @a id is not found, false is returned - **/ -bool ssr::Scene::get_source_mute_state(id_t id) const -{ - bool state; - if (_get_source_member(id, &Source::mute, state)) return state; - return false; -} - -/** _. - * @param id ID of the source - * @return source name - * @warning If @a id is not found, an empty string is returned - **/ -std::string ssr::Scene::get_source_name(id_t id) const -{ - std::string name; - if (_get_source_member(id, &Source::name, name)) return name; - return std::string(""); -} - -bool ssr::Scene::get_source_position_fixed(id_t id) const -{ - bool boolean; - if (_get_source_member(id, &Source::fixed_position, boolean)) return boolean; - - return false; -} - -std::string ssr::Scene::get_source_properties_file(id_t id) const -{ - std::string name; - if (_get_source_member(id, &Source::properties_file, name)) return name; - return std::string(""); -} diff --git a/src/scene.h b/src/scene.h index 64457e7c..9154b583 100644 --- a/src/scene.h +++ b/src/scene.h @@ -30,315 +30,258 @@ #ifndef SSR_SCENE_H #define SSR_SCENE_H +#include // for assert() #include #include -#include // for assert() -#include -#include "subscriber.h" -#include "maptools.h" // for get_item() -#include "source.h" -#include "loudspeaker.h" +#include "api.h" +#include "ssr_global.h" // for VERBOSE() namespace ssr { /// %Scene. Contains current location and other data about sources, the -// reference point and other things. -class Scene : public Subscriber +/// reference point and other things. +class Scene : public api::SceneControlEvents + , public api::SceneInformationEvents { - public: - /// A map of sources. - using source_map_t = std::map; - /// A vector of loudspeakers. - using loudspeakers_t = std::vector; - - Scene(); ///< ctor - ~Scene(); ///< dtor - - // from Subscriber - virtual void set_loudspeakers(const Loudspeaker::container_t& container); - virtual void new_source(id_t id); - virtual void delete_source(id_t id); - virtual void delete_all_sources(); - virtual bool set_source_position(id_t id, const Position& position); - virtual bool set_source_orientation(id_t id - , const Orientation& orientation); - virtual bool set_source_gain(id_t id, const float& gain); - - virtual bool set_source_signal_level(id_t id, const float& level); - - virtual bool set_source_mute(id_t id, const bool& mute); - virtual bool set_source_name(id_t id, const std::string& name); - virtual bool set_source_properties_file(id_t id, const std::string& name); - virtual bool set_source_model(id_t id, const Source::model_t& model); - virtual bool set_source_port_name(id_t id, const std::string& port_name); - virtual bool set_source_file_name(id_t id, const std::string& file_name); - virtual bool set_source_file_channel(id_t id, const int& file_channel); - virtual bool set_source_position_fixed(id_t id, const bool& fixed); - virtual bool set_source_file_length(id_t id, const long int& length); - - virtual void set_reference_position(const Position& position); - virtual void set_reference_orientation(const Orientation& orientation); - - virtual void set_reference_offset_position(const Position& position); - virtual void set_reference_offset_orientation(const Orientation& orientation); - - virtual void set_master_volume(float volume); +public: + struct Source + { + Pos position{}; + Rot rotation{}; + bool fixed{false}; ///< Static position/rotation or not + + std::string name; + float volume{1}; + bool mute{false}; + /// Source model (=type). Most renderers support "point" and "plane". + /// Theoretically, other models could be supported, like "line", + /// "directional", "extended", ... + std::string model{"point"}; + + std::map> properties; + }; + + template + void for_each_source(F f) const + { + for (const auto& [id, source]: _source_map) { f(id, source); } + } - virtual void set_decay_exponent(float exponent); + const Source* get_source(id_t id) const + { + auto item = _source_map.find(id); + if (item == _source_map.end()) { return nullptr; } + return &(item->second); + } - virtual void set_amplitude_reference_distance(float dist); + /// Get string ID given one-based source number. + /// @see api::Controller::get_source_id() + std::string get_source_id(unsigned source_number) const + { + if (!source_number || source_number > _source_ids.size()) { return {}; } + return _source_ids[source_number - 1]; + } - virtual void set_master_signal_level(float level); + /// Get the one-based source number given a string ID. + /// @see api::Controller::get_source_number() + unsigned int get_source_number(id_t source_id) const + { + auto result = std::find(_source_ids.begin(), _source_ids.end(), source_id); + if (result == _source_ids.end()) + { + return 0; + } + return result - _source_ids.begin() + 1; + } - virtual void set_cpu_load(float load); + Pos get_reference_position() const { return _reference_position; } + Rot get_reference_rotation() const { return _reference_rotation; } - virtual void set_sample_rate(int sample_rate); + bool get_auto_rotation() const + { + return _auto_rotate_sources; + } - virtual void set_source_output_levels(id_t id, float* first, float* last); + void get_data(SceneControlEvents* subscriber) + { + assert(subscriber); + subscriber->auto_rotate_sources(_auto_rotate_sources); + subscriber->reference_position(_reference_position); + subscriber->reference_rotation(_reference_rotation); + subscriber->master_volume(_master_volume); + // TODO: master volume correction? + subscriber->decay_exponent(_decay_exponent); + subscriber->amplitude_reference_distance(_amplitude_reference_distance); + + this->for_each_source([subscriber](auto id, auto& source) { + subscriber->source_position(id, source.position); + subscriber->source_rotation(id, source.rotation); + subscriber->source_volume(id, source.volume); + subscriber->source_mute(id, source.mute); + subscriber->source_name(id, source.name); + subscriber->source_model(id, source.model); + subscriber->source_fixed(id, source.fixed); + }); + } - virtual void set_processing_state(bool state); - //virtual void set_transport_state(JackClient::State state); - virtual void set_transport_state( - const std::pair& state); + void get_data(SceneInformationEvents* subscriber) + { + this->for_each_source([subscriber](auto id, auto& source) { + // Sources have to be created first, then they can be updated + subscriber->new_source(id); + for (const auto& item: source.properties) + { + subscriber->source_property(id, item.first, item.second); + } + }); + } - virtual void set_auto_rotation(bool auto_rotate_sources); +private: + template + void _set_source_member(id_t id, PTM member, T&& arg) + { + auto src = _source_map.find(id); + if (src == _source_map.end()) + { + WARNING("Source \"" << id << "\" doesn't exist!"); + } + else + { + src->second.*member = std::forward(arg); + } + } - loudspeakers_t::size_type get_number_of_loudspeakers() const; + // SceneControlEvents - // std::pair get_transport_state() const; + void auto_rotate_sources(bool auto_rotate) override + { + _auto_rotate_sources = auto_rotate; + } - /// get master volume - float get_master_volume() const; + void delete_source(id_t id) override + { + auto erased = _source_map.erase(id); + assert(erased < 2); + if (erased == 0) + { + WARNING("Source \"" << id << "\" not available, couldn't delete"); + } + else + { + std::remove(_source_ids.begin(), _source_ids.end(), id); + } + } - /// get master volume with amplitude correction considered - float get_corrected_master_volume() const; + void source_position(id_t id, const Pos& position) override + { + _set_source_member(id, &Source::position, position); + } - /// get amplitude decay exponent - float get_decay_exponent() const; + void source_rotation(id_t id, const Rot& rotation) override + { + _set_source_member(id, &Source::rotation, rotation); + } - /// get amplitude reference distance - float get_amplitude_reference_distance() const; + void source_volume(id_t id, float volume) override + { + _set_source_member(id, &Source::volume, volume); + } - /// get master audio level - float get_master_signal_level() const; + void source_mute(id_t id, bool mute) override + { + _set_source_member(id, &Source::mute, mute); + } - /// get CPU load in percent estimated by JACK - float get_cpu_load() const; + void source_name(id_t id, const std::string& name) override + { + _set_source_member(id, &Source::name, name); + } - /// get current sample rate - int get_sample_rate() const; + void source_model(id_t id, const std::string& model) override + { + _set_source_member(id, &Source::model, model); + } - /// get source - Source get_source(id_t id) const; - /// get source position - std::unique_ptr get_source_position(id_t id) const; - /// get source orientation - std::unique_ptr get_source_orientation(id_t id) const; - /// get source model (=type) - Source::model_t get_source_model(id_t id) const; - /// get source gain - float get_source_gain(id_t id) const; - /// get source mute state - bool get_source_mute_state(id_t id) const; - /// get source name - std::string get_source_name(id_t id) const; + void source_fixed(id_t id, bool fixed) override + { + _set_source_member(id, &Source::fixed, fixed); + } - bool get_source_position_fixed(id_t id) const; + void reference_position(const Pos& position) override + { + _reference_position = position; + } - std::string get_source_properties_file(id_t id) const; + void reference_rotation(const Rot& rotation) override + { + _reference_rotation = rotation; + } - /// check if renderer is processing - bool get_processing_state() const; - bool is_playing() const; - jack_nframes_t get_transport_position() const; + void master_volume(float volume) override + { + _master_volume = volume; + } - bool get_auto_rotation() const; + void decay_exponent(float exponent) override + { + _decay_exponent = exponent; + } - // temporarily with inline implementation - // should return 0 in case of doubt. - // will be removed in the near future - /// deprecated! - inline virtual int get_max_no_of_sources() const - { - return 100; - } + void amplitude_reference_distance(float distance) override + { + _amplitude_reference_distance = distance; + } - /// get a list of all sources. - /// @param container (initially empty) list of sources. - /// @pre - /// For the template argument @a T you can use any type which has a - /// conversion constructor for the Source type. Speaking more exactly, you - /// need a constructor of the following form: - /// T::T(const pair&) - /// @par - /// Your type @a T can include any of the members of Source, this way you - /// can obtain any subset of data you desire. If you want all available - /// data, just use the Source type itself. - /// @warning if a std::vector is used as container, the function - /// get_number_of_sources() is called to reserve the - /// necessary memory to avoid memory re-allocation. - /// - /// Because a fancy template template is used, any container type with one - /// template argument can be used, like std::list, std::vector, ... - /// as long as it has the following member functions: .begin(), .end(), - /// .push_back(). - template class Container, typename T, - typename... Args> - void get_sources(Container& container) const - { - assert(container.empty()); - - // the following struct container_traits is declared in the private part - // of the Scene class and defined further down this file. - - if (container_traits>::has_reserve) - { - container_traits>::reserve(container, - _source_map.size()); - } - for (const auto& source: _source_map) - { - // type conversion constructor T::T(const pair&) needed! - container.push_back(T(source)); - } - } + // SceneInformationEvents - /// get a list of all loudspeakers. - /// @warning This doesn't return a valid value for the "active" field! (if - /// your type T has it) - /// - /// ID of the loudspeaker == its position in the container - /// container[0] has ID 1, container[1] has ID 2, ... - template class Container, typename T, - typename Allocator> - void get_loudspeakers(Container& container, - bool absolute_position = true) const + void new_source(id_t id) override + { + VERBOSE("Adding source \"" << id << "\" to source map"); + auto [iter, inserted] = _source_map.try_emplace(id); + if (inserted) { - assert(container.empty()); - - if (container_traits>::has_reserve) - { - container_traits>::reserve(container, - _loudspeakers.size()); - } - - for (loudspeakers_t::const_iterator i = _loudspeakers.begin(); - i != _loudspeakers.end(); ++i) - { - T temp(*i); // copy ctor. is called - if (absolute_position) - { - // T has to be derived from DirectionalPoint - temp.DirectionalPoint::operator=(temp.transform(_reference)); - } - container.push_back(temp); // copy ctor. is called again - } + _source_ids.push_back(id); } - - /// get current reference position/orientation. - /// @return position/orientation of the reference point. - DirectionalPoint get_reference() const + else { - return _reference; + ERROR("Source \"" << id << "\" already existed in the source map"); } + } - DirectionalPoint get_reference_offset() const + void source_property(id_t id, const std::string& key + , const std::string& value) override + { + auto src = _source_map.find(id); + if (src == _source_map.end()) { - return _reference_offset; + ERROR("Source \"" << id << "\" doesn't exist!"); } - - protected: - source_map_t _source_map; ///< container for sources - - private: - loudspeakers_t _loudspeakers; - DirectionalPoint _reference; ///< position/orientation of the reference - DirectionalPoint _reference_offset; - float _master_volume; ///< master volume (linear) - float _master_volume_correction; ///< dito (linear scale) - float _decay_exponent; ///< dito - float _amplitude_reference_distance; ///< distance where plane sources are - ///< as loud as the other source types - float _master_signal_level; ///< instantaneous overall signal level (linear) - float _cpu_load; ///< CPU load in percent - int _sample_rate; ///< sample rate - /// order of mirror sources. if 0, mirror sources are deactivated - int mirror_order; - bool _processing_state; ///< is renderer processing? - bool _transport_playing; - jack_nframes_t _transport_position; ///< current position in the audio file in samples - bool _auto_rotate_sources; - - template bool _set_source_member( - id_t id, PointerToMember member, const T& arg) + else { - auto source_ptr = maptools::get_item(_source_map, id); - if (!source_ptr) - { - VERBOSE("Source " << id << " doesn't exist!"); - return false; - } - source_ptr->*member = arg; - return true; + src->second.properties[key] = value; } + } - /// helper function template for get_*() - template bool _get_source_member( - id_t id, PointerToMember member, T& arg) const - { - const Source* const source_ptr = maptools::get_item(_source_map, id); - if (!source_ptr) - { - VERBOSE("Source " << id << " doesn't exist!"); - return false; - } - arg = source_ptr->*member; - return true; - } + std::map> _source_map; + /// List of source IDs, ordered by creation time. + std::vector _source_ids; - // forward declaration of the container traits class - template struct container_traits; // nested - // partial specialization: - template struct container_traits>; -}; + Pos _reference_position{}; + Rot _reference_rotation{}; -//////////////////////////////////////////////////////////////////////////////// -// Definition of the nested struct container_traits -//////////////////////////////////////////////////////////////////////////////// + float _master_volume{1}; ///< master volume (linear) + float _master_volume_correction{1}; ///< dito (linear scale) + float _decay_exponent{1}; ///< dito + /// distance where plane sources are as loud as the other source types + float _amplitude_reference_distance{3}; -/// traits class to check if a container has a reserve() member function -// default template for any type of container -template struct Scene::container_traits -{ - static const bool has_reserve = false; - template static void reserve(Container&, const U&) - { - // does nothing, because generally, there is no reserve() member - // function an we will never call it anyway. - // We only declare it to get no errors from the compiler. - } -}; + bool _auto_rotate_sources{true}; -/// traits class to check if a container has a reserve() member function -// partial specialization of the above template. -template -struct Scene::container_traits> -{ - static const bool has_reserve = true; - template static void reserve(std::vector& v, const U& space) - { - v.reserve(space); - } + // TODO: this should be removed at some point: + friend class LegacySceneWrapper; }; -// if any other STL container has a reserve() member function it should be -// added here as a specialization. - -// TODO: allow Allocator template argument with default std::allocator -// but: default template arguments may not be used in partial specializations -// introducing this argument into the default template as well would make this -// already quite complicated template thingy even more complicated! } // namespace ssr diff --git a/src/ssr_global.h b/src/ssr_global.h index 8f262071..5776ad85 100644 --- a/src/ssr_global.h +++ b/src/ssr_global.h @@ -36,9 +36,6 @@ namespace ssr { -/// Used as unique identifier for sources, loudspeakers, ... -using id_t = unsigned int; - /** Verbosity level. * @arg 0 - Only errors and warnings are shown. * @arg 1 - A few more messages are shown. diff --git a/src/subscriber.h b/src/subscriber.h deleted file mode 100644 index 8839d8fb..00000000 --- a/src/subscriber.h +++ /dev/null @@ -1,179 +0,0 @@ -/****************************************************************************** - * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * - * Copyright © 2006-2012 Quality & Usability Lab, * - * Telekom Innovation Laboratories, TU Berlin * - * * - * This file is part of the SoundScape Renderer (SSR). * - * * - * The SSR is free software: you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free * - * Software Foundation, either version 3 of the License, or (at your option) * - * any later version. * - * * - * The SSR 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 General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along * - * with this program. If not, see . * - * * - * The SSR is a tool for real-time spatial audio reproduction providing a * - * variety of rendering algorithms. * - * * - * http://spatialaudio.net/ssr ssr@spatialaudio.net * - ******************************************************************************/ - -/// @file -/// Abstract %Subscriber (definition). - -#ifndef SSR_SUBSCRIBER_H -#define SSR_SUBSCRIBER_H - -#include -#include - -#include "ssr_global.h" -#include "source.h" -#include "loudspeaker.h" - -namespace ssr -{ - -using jack_nframes_t = uint32_t; // from - -/** Abstract interface to Scene class (and similar classes). - **/ -struct Subscriber -{ - /// dtor. does nothing. - virtual ~Subscriber() {} - - virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers) = 0; - - /// Create a new source with default values. - /// @param id ID of the new source - virtual void new_source(id_t id) {(void)id;} - - /// Remove a source from the Scene. - /// @param id ID of the source - virtual void delete_source(id_t id) = 0; - - /// delete all sources, quite obviously. - virtual void delete_all_sources() = 0; - - /// Set source position. - /// @param id ID of the source - /// @param position new position - virtual bool set_source_position(id_t id, const Position& position) = 0; - - /// Set source orientation. - /// @param id ID of the source - /// @param orientation new orientation - virtual bool set_source_orientation(id_t id, - const Orientation& orientation) = 0; - - /// Set source gain. - /// @param id ID of the source - /// @param gain new gain - /// @attention 2nd argument is given by const reference to facilitate the - /// _publish() function in the Controller class. - virtual bool set_source_gain(id_t id, const float& gain) = 0; - - /// Set instantaneous level of audio stream. - /// @param id ID of the source - /// @param level new level (linear scale) - /// @param intitiator not implemented - virtual bool set_source_signal_level(const id_t id, - const float& level) = 0; - - /// mute/unmute source. - /// @param id ID of the source - /// @param mute mute if @b true, unmute if @b false - /// @attention 2nd argument is given by const reference to facilitate the - /// _publish() function in the Controller class. - virtual bool set_source_mute(id_t id, const bool& mute) = 0; - - /// Set source name. - /// @param id ID of the source - /// @param name new name - virtual bool set_source_name(id_t id, const std::string& name) = 0; - - /// Set name of file containing impulse responses. - /// @param id ID of the source - /// @param name file name - virtual bool set_source_properties_file(id_t id, const std::string& name) = 0; - - /// Set source model (=type). - /// @param id ID of the source - /// @param model new model - /// @attention 2nd argument is given by const reference to facilitate the - /// _publish() function in the Controller class. - virtual bool set_source_model(id_t id, const Source::model_t& model) = 0; - - /// Set port name of a source. - /// @param id ID of the source - /// @param port_name JACK port name - virtual bool set_source_port_name(id_t id, const std::string& port_name) = 0; - - virtual bool set_source_file_name(id_t id, const std::string& file_name) = 0; - - virtual bool set_source_file_channel(id_t id, const int& file_channel) = 0; - - virtual bool set_source_position_fixed(id_t id, const bool& fixed) = 0; - - /// Set length of associated audio file. - /// @param id ID of the source - /// @param length length in samples - /// @attention 2nd argument is given by const reference to facilitate the - /// _publish() function in the Controller class. - virtual bool set_source_file_length(id_t id, const long int& length) = 0; - - /// Set reference position. - /// @param position new position - virtual void set_reference_position(const Position& position) = 0; - - /// Set reference orientation. - /// @param orientation new orientation - virtual void set_reference_orientation(const Orientation& orientation) = 0; - - virtual void set_reference_offset_position(const Position& position) = 0; - virtual void set_reference_offset_orientation(const Orientation& orientation) = 0; - - /// Set master volume. - /// @param volume volume - virtual void set_master_volume(float volume) = 0; - - /// Set amplitude decay exponent. - /// @param exponent amplitude decay exponent - virtual void set_decay_exponent(float exponent) = 0; - - /// Set amplitude reference distance. - /// @param distance amplitude reference distance - virtual void set_amplitude_reference_distance(float distance) = 0; - - /// Set master volume. - /// @param level instantaneous overall audio level (linear scale) - virtual void set_master_signal_level(float level) = 0; - - /// Set CPU load. - /// @param load CPU load in percent - virtual void set_cpu_load(float load) = 0; - - /// Set sample rate. - /// @param sample_rate sample rate - virtual void set_sample_rate(int sample_rate) = 0; - - virtual void set_source_output_levels(id_t id, float* first, float* last) = 0; - - /// Update information about audio processing; - virtual void set_processing_state(bool state) = 0; - - virtual void set_transport_state(const std::pair& state) = 0; - - virtual void set_auto_rotation(bool auto_rotate_sources) = 0; -}; - -} // namespace ssr - -#endif diff --git a/src/trackerintersense.cpp b/src/trackerintersense.cpp index a3bec37c..e771be57 100644 --- a/src/trackerintersense.cpp +++ b/src/trackerintersense.cpp @@ -36,11 +36,12 @@ #include #include "trackerintersense.h" -#include "publisher.h" +#include "api.h" // for Publisher +#include "legacy_orientation.h" // for Orientation #include "ssr_global.h" #include "posixpathtools.h" -ssr::TrackerInterSense::TrackerInterSense(Publisher& controller +ssr::TrackerInterSense::TrackerInterSense(api::Publisher& controller , const std::string& ports, const unsigned int read_interval) : Tracker() , _controller(controller) @@ -137,8 +138,8 @@ ssr::TrackerInterSense::~TrackerInterSense() } ssr::TrackerInterSense::ptr_t -ssr::TrackerInterSense::create(Publisher& controller, const std::string& ports - , const unsigned int read_interval) +ssr::TrackerInterSense::create(api::Publisher& controller + , const std::string& ports, const unsigned int read_interval) { ptr_t temp; // temp = NULL try @@ -190,12 +191,12 @@ void* ssr::TrackerInterSense::thread(void *arg) { #ifdef HAVE_INTERSENSE_404 ISD_GetTrackingData(_tracker_h, &tracker_data); - _controller.set_reference_orientation( + _controller.take_control()->reference_offset_rotation( Orientation(-tracker_data.Station[0].Euler[0] + 90.0f)); #else ISD_GetData(_tracker_h, &tracker_data); - _controller.set_reference_orientation( + _controller.take_control()->reference_offset_rotation( Orientation(-static_cast(tracker_data.Station[0].Orientation[0]) + 90.0f)); #endif diff --git a/src/trackerintersense.h b/src/trackerintersense.h index f96dabd4..38651c66 100644 --- a/src/trackerintersense.h +++ b/src/trackerintersense.h @@ -41,7 +41,7 @@ namespace ssr { -struct Publisher; +namespace api { struct Publisher; } /// Intersense InertiaCube3 head tracker class TrackerInterSense : public Tracker @@ -52,17 +52,17 @@ class TrackerInterSense : public Tracker virtual ~TrackerInterSense(); ///< destructor /// "named constructor" - static ptr_t create(Publisher& controller, const std::string& ports = "", - const unsigned int read_interval = 20); + static ptr_t create(api::Publisher& controller + , const std::string& ports = "", const unsigned int read_interval = 20); virtual void calibrate(); private: /// constructor - TrackerInterSense(Publisher& controller, const std::string& ports + TrackerInterSense(api::Publisher& controller, const std::string& ports , const unsigned int read_interval); - Publisher& _controller; + api::Publisher& _controller; /// interval in ms to wait after each read cycle unsigned int _read_interval; diff --git a/src/trackerpolhemus.cpp b/src/trackerpolhemus.cpp index c13740e9..c31dd9ff 100644 --- a/src/trackerpolhemus.cpp +++ b/src/trackerpolhemus.cpp @@ -36,19 +36,20 @@ #include // std::this_thread::sleep_for #include // std::chrono::milliseconds +#include "api.h" // for Publisher +#include "legacy_orientation.h" // for Orientation #include "trackerpolhemus.h" -#include "publisher.h" #include "ssr_global.h" #include "apf/stringtools.h" using apf::str::A2S; -ssr::TrackerPolhemus::TrackerPolhemus(Publisher& controller +ssr::TrackerPolhemus::TrackerPolhemus(api::Publisher& controller , const std::string& type, const std::string& ports) : Tracker() , _controller(controller) , _stopped(false) - , _az_corr(90.0f) + , _az_corr(0.0f) , _thread_id(0) { if (ports == "") @@ -153,8 +154,8 @@ ssr::TrackerPolhemus::~TrackerPolhemus() } ssr::TrackerPolhemus::ptr_t -ssr::TrackerPolhemus::create(Publisher& controller, const std::string& type - , const std::string& ports) +ssr::TrackerPolhemus::create(api::Publisher& controller + , const std::string& type, const std::string& ports) { ptr_t temp; // temp = NULL try @@ -193,7 +194,7 @@ ssr::TrackerPolhemus::_open_serial_port(const char *portname) void ssr::TrackerPolhemus::calibrate() { - _az_corr = _current_data.azimuth + 90.0f; + _az_corr = _current_data.azimuth; } void @@ -299,7 +300,7 @@ ssr::TrackerPolhemus::thread(void *arg) >> _current_data.elevation >> _current_data.roll; - _controller.set_reference_orientation( + _controller.take_control()->reference_offset_rotation( Orientation(-_current_data.azimuth + _az_corr)); }; return arg; diff --git a/src/trackerpolhemus.h b/src/trackerpolhemus.h index 6cc3d226..727400de 100644 --- a/src/trackerpolhemus.h +++ b/src/trackerpolhemus.h @@ -40,7 +40,7 @@ namespace ssr { -struct Publisher; // forward declaration +namespace api { struct Publisher; } /// Polhemus Fastrak/Patriot trackers class TrackerPolhemus : public Tracker @@ -51,14 +51,14 @@ class TrackerPolhemus : public Tracker virtual ~TrackerPolhemus(); ///< destructor /// "named constructor" - static ptr_t create(Publisher& controller, const std::string& type + static ptr_t create(api::Publisher& controller, const std::string& type , const std::string& ports); virtual void calibrate(); private: /// constructor - TrackerPolhemus(Publisher& controller, const std::string& type + TrackerPolhemus(api::Publisher& controller, const std::string& type , const std::string& ports); struct tracker_data_t @@ -78,7 +78,7 @@ class TrackerPolhemus : public Tracker {} }; - Publisher& _controller; + api::Publisher& _controller; tracker_data_t _current_data; diff --git a/src/trackerrazor.cpp b/src/trackerrazor.cpp index b029d920..7bce6a24 100644 --- a/src/trackerrazor.cpp +++ b/src/trackerrazor.cpp @@ -30,7 +30,8 @@ #include "trackerrazor.h" -ssr::TrackerRazor::TrackerRazor(Publisher& controller, const std::string& ports) +ssr::TrackerRazor::TrackerRazor(api::Publisher& controller + , const std::string& ports) : Tracker() , _controller(controller) , _current_azimuth(0.0f) @@ -74,7 +75,7 @@ ssr::TrackerRazor::TrackerRazor(Publisher& controller, const std::string& ports) } ssr::TrackerRazor::ptr_t -ssr::TrackerRazor::create(Publisher& controller, const std::string& ports) +ssr::TrackerRazor::create(api::Publisher& controller, const std::string& ports) { ptr_t temp; // temp = NULL try diff --git a/src/trackerrazor.h b/src/trackerrazor.h index 64882507..e24a4180 100644 --- a/src/trackerrazor.h +++ b/src/trackerrazor.h @@ -31,13 +31,17 @@ #ifndef SSR_TRACKERRAZOR_H #define SSR_TRACKERRAZOR_H +#include "legacy_orientation.h" // for Orientation +#include "ssr_global.h" // for ERROR #include "tracker.h" // base class + #include "razor-ahrs/RazorAHRS.h" -#include "publisher.h" namespace ssr { +namespace api { struct Publisher; } + /// Razor AHRS tracker class TrackerRazor : public Tracker { @@ -45,7 +49,7 @@ class TrackerRazor : public Tracker using ptr_t = std::unique_ptr; /// "named constructor" - static ptr_t create(Publisher& controller, const std::string& ports); + static ptr_t create(api::Publisher& controller, const std::string& ports); /// destructor ~TrackerRazor() @@ -53,26 +57,28 @@ class TrackerRazor : public Tracker if (_tracker != nullptr) delete _tracker; } - virtual void calibrate() { _az_corr = _current_azimuth + 90.0f; } + virtual void calibrate() { _az_corr = _current_azimuth; } private: /// constructor - TrackerRazor(Publisher& controller, const std::string& ports); + TrackerRazor(api::Publisher& controller, const std::string& ports); /// Razor AHRS callback functions void on_data(const float ypr[]) { + // TODO: get 3D rotation _current_azimuth = ypr[0]; if (_init_az_corr) { calibrate(); _init_az_corr = false; } - _controller.set_reference_orientation(Orientation(-_current_azimuth + _az_corr)); + _controller.take_control()->reference_offset_rotation( + Orientation(-_current_azimuth + _az_corr)); } void on_error(const std::string &msg) { ERROR("Razor AHRS: " << msg); } - Publisher& _controller; + api::Publisher& _controller; volatile float _current_azimuth; volatile float _az_corr; volatile bool _init_az_corr; diff --git a/src/trackervrpn.cpp b/src/trackervrpn.cpp index e5b0166d..00ab076c 100644 --- a/src/trackervrpn.cpp +++ b/src/trackervrpn.cpp @@ -30,17 +30,18 @@ #include "trackervrpn.h" #include // for runtime_error +#include // for std::atan2() -#include "publisher.h" +#include "api.h" // for Publisher +#include "legacy_orientation.h" // for Orientation #include "ssr_global.h" -#include "apf/math.h" // for pi - -ssr::TrackerVrpn::TrackerVrpn(Publisher& controller, const std::string& address) +ssr::TrackerVrpn::TrackerVrpn(api::Publisher& controller + , const std::string& address) : vrpn_Tracker_Remote(address.c_str()) , _controller(controller) , _stopped(false) - , _az_corr(90.0f) + , _az_corr(0.0f) , _thread_id(0) { VERBOSE("Starting VRPN tracker \"" << address << "\""); @@ -64,7 +65,7 @@ ssr::TrackerVrpn::~TrackerVrpn() } ssr::TrackerVrpn::ptr_t -ssr::TrackerVrpn::create(Publisher& controller, const std::string& ports) +ssr::TrackerVrpn::create(api::Publisher& controller, const std::string& ports) { ptr_t temp; // temp = NULL try @@ -81,7 +82,7 @@ ssr::TrackerVrpn::create(Publisher& controller, const std::string& ports) void ssr::TrackerVrpn::calibrate() { - _az_corr = _current_azimuth + 90.0f; + _az_corr = _current_azimuth; } void @@ -134,9 +135,12 @@ ssr::TrackerVrpn::vrpn_change_handler(const vrpn_TRACKERCB t) double y = t.quat[2]; double z = t.quat[3]; - // calculate yaw (azimuth) from quaternions - double azi = atan2(2*(w*x+y*z),1-2*(x*x+y*y))*(180/apf::math::pi()); + // TODO: store _az_corr as quaternion and directly set 3D rotation + + // calculate yaw (azimuth) (in radians) from quaternions + double azi = std::atan2(2*(w*x+y*z),1-2*(x*x+y*y)); _current_azimuth = azi; - _controller.set_reference_orientation(Orientation(-azi + _az_corr)); + _controller.take_control()->reference_offset_rotation( + Orientation(-azi + _az_corr)); } diff --git a/src/trackervrpn.h b/src/trackervrpn.h index 1da819b0..33953111 100644 --- a/src/trackervrpn.h +++ b/src/trackervrpn.h @@ -41,7 +41,7 @@ namespace ssr { -struct Publisher; // forward declaration +namespace api { struct Publisher; } /// VRPN tracker class TrackerVrpn : public vrpn_Tracker_Remote, public Tracker @@ -52,15 +52,15 @@ class TrackerVrpn : public vrpn_Tracker_Remote, public Tracker virtual ~TrackerVrpn(); /// "named constructor" - static ptr_t create(Publisher& controller, const std::string& portsz); + static ptr_t create(api::Publisher& controller, const std::string& ports); virtual void calibrate(); void set_value(double azi); private: - TrackerVrpn(Publisher& controller, const std::string& ports); + TrackerVrpn(api::Publisher& controller, const std::string& ports); - Publisher& _controller; + api::Publisher& _controller; std::string _address; diff --git a/src/vbaprenderer.h b/src/vbaprenderer.h index d83b4142..b3510c77 100644 --- a/src/vbaprenderer.h +++ b/src/vbaprenderer.h @@ -290,7 +290,7 @@ VbapRenderer::load_reproduction_setup() for (const auto& out: rtlist_proxy(this->get_output_list())) { - if (out.model == Loudspeaker::subwoofer) + if (out.model == LegacyLoudspeaker::subwoofer) { // TODO: put subwoofers in separate list? (for get_output_levels()) } diff --git a/src/wfsrenderer.h b/src/wfsrenderer.h index bd1fcdff..e1549c74 100644 --- a/src/wfsrenderer.h +++ b/src/wfsrenderer.h @@ -250,7 +250,7 @@ class WfsRenderer::Source : public _base::Source void WfsRenderer::Source::_process() { - if (this->model == ::Source::plane) + if (this->model == "plane") { // do nothing, focused-ness is irrelevant for plane waves _focused = false; @@ -261,7 +261,7 @@ void WfsRenderer::Source::_process() for (const auto& out: rtlist_proxy(_input.parent.get_output_list())) { // subwoofers have to be ignored! - if (out.model == Loudspeaker::subwoofer) continue; + if (out.model == LegacyLoudspeaker::subwoofer) continue; // TODO: calculate with inner product @@ -320,7 +320,7 @@ WfsRenderer::RenderFunction::select(SourceChannel& in) sample_type weighting_factor = 1; float float_delay = 0; - auto ls = Loudspeaker(_out); + auto ls = LegacyLoudspeaker(_out); auto src_pos = in.source.position; // TODO: shortcut if in.source.weighting_factor == 0 @@ -332,23 +332,22 @@ WfsRenderer::RenderFunction::select(SourceChannel& in) float source_ls_distance = (ls.position - src_pos).length(); - switch (in.source.model) // check if point source or plane wave or ... + const std::string& model = in.source.model; + if (model == "point") { - case ::Source::point: - if (ls.model == Loudspeaker::subwoofer) - { - // the delay is calculated to be correct on the reference position - // delay can be negative! - float_delay = (src_pos - ref_off.position).length() - - reference_distance; - - // setting the subwoofer amplitude to 1 is the unwritten standard - // (cf. AAP renderer) - weighting_factor = 1.0f; - - break; // step out of switch - } - + if (ls.model == LegacyLoudspeaker::subwoofer) + { + // the delay is calculated to be correct on the reference position + // delay can be negative! + float_delay = (src_pos - ref_off.position).length() + - reference_distance; + + // setting the subwoofer amplitude to 1 is the unwritten standard + // (cf. AAP renderer) + weighting_factor = 1.0f; + } + else + { float_delay = source_ls_distance; assert(float_delay >= 0); @@ -403,14 +402,12 @@ WfsRenderer::RenderFunction::select(SourceChannel& in) { // ignored focused point source weighting_factor = 0; - break; } } else // non-focused and weighting_factor < 0 { // ignored non-focused point source weighting_factor = 0; - break; } } else if(weighting_factor > 0.0f) // positive weighting factor @@ -429,28 +426,28 @@ WfsRenderer::RenderFunction::select(SourceChannel& in) else // focused { // ignored focused point source - break; + weighting_factor = 0; } } else { - // this should never happen: Weighting factor is 0 or +-Inf or NaN! - break; + // Weighting factor is 0 (or +-Inf or NaN) } - break; - - case ::Source::plane: - if (ls.model == Loudspeaker::subwoofer) - { - weighting_factor = 1.0f; // TODO: is this correct? - // the delay is calculated to be correct on the reference position - // delay can be negative! - float_delay - = DirectionalPoint(in.source.position, in.source.orientation) - .plane_to_point_distance(ref_off.position) - reference_distance; - break; // step out of switch - } - + } + } + else if (model == "plane") + { + if (ls.model == LegacyLoudspeaker::subwoofer) + { + weighting_factor = 1.0f; // TODO: is this correct? + // the delay is calculated to be correct on the reference position + // delay can be negative! + float_delay + = DirectionalPoint(in.source.position, in.source.orientation) + .plane_to_point_distance(ref_off.position) - reference_distance; + } + else + { // weighting factor is determined by the cosine of the angle // difference between plane wave direction and loudspeaker direction weighting_factor = cos(angle(in.source.orientation, ls.orientation)); @@ -459,35 +456,37 @@ WfsRenderer::RenderFunction::select(SourceChannel& in) { // ignored plane wave weighting_factor = 0; - break; } - - float_delay = DirectionalPoint(in.source.position, in.source.orientation) - .plane_to_point_distance(ls.position); - - if (float_delay < 0.0) - { - // "focused" plane wave - } - else // positive delay + else { - // plane wave - } - break; + float_delay = DirectionalPoint(in.source.position + , in.source.orientation) + .plane_to_point_distance(ls.position); - default: - //WARNING("Unknown source model"); - break; - } // switch source model + if (float_delay < 0.0) + { + // "focused" plane wave + } + else // positive delay + { + // plane wave + } + } + } + } + else + { + //WARNING("Unknown source model"); + } #if defined(WEIGHTING_OLD) - if (in.source.model == ::Source::point) + if (model == "point") { // compensate for inherent distance decay (approx. 1/sqrt(r)) // no compensation closer to 0.5 m to the reference // this is the same operation for focused and non-focused sources // exclude subwoofers as there is no inherent amplitude decay - if (ls.model != Loudspeaker::subwoofer) + if (ls.model != LegacyLoudspeaker::subwoofer) { weighting_factor *= std::sqrt(std::max((src_pos - ref_off.position).length(), 0.5f));