1
/******************************************************************************
2
* Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock *
3
* Copyright © 2006-2012 Quality & Usability Lab, *
4
* Telekom Innovation Laboratories, TU Berlin *
6
* This file is part of the SoundScape Renderer (SSR). *
8
* The SSR is free software: you can redistribute it and/or modify it under *
9
* the terms of the GNU General Public License as published by the Free *
10
* Software Foundation, either version 3 of the License, or (at your option) *
11
* any later version. *
13
* The SSR is distributed in the hope that it will be useful, but WITHOUT ANY *
14
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS *
15
* FOR A PARTICULAR PURPOSE. *
16
* See the GNU General Public License for more details. *
18
* You should have received a copy of the GNU General Public License along *
19
* with this program. If not, see <http://www.gnu.org/licenses/>. *
21
* The SSR is a tool for real-time spatial audio reproduction providing a *
22
* variety of rendering algorithms. *
24
* http://spatialaudio.net/ssr ssr@spatialaudio.net *
25
******************************************************************************/
28
/// Renderer base class.
30
#ifndef SSR_RENDERERBASE_H
31
#define SSR_RENDERERBASE_H
35
#include "apf/mimoprocessor.h"
36
#include "apf/shareddata.h"
37
#include "apf/container.h" // for distribute_list()
38
#include "apf/parameter_map.h"
39
#include "apf/math.h" // for dB2linear()
41
// TODO: avoid multiple ambiguous "Source" classes
42
#include "source.h" // for ::Source::model_t
46
#ifndef SSR_QUERY_POLICY
47
#define SSR_QUERY_POLICY apf::disable_queries
53
/** Renderer base class.
54
* @todo more documentation!
56
* The parallel rendering engine uses the non-blocking datastructure RtList to
57
* communicate between realtime and non-realtime threads.
58
* All non-realtime accesses to RtList%s have to be locked with
59
* get_scoped_lock() to ensure single-reader/single-writer operation.
61
template<typename Derived>
62
class RendererBase : public apf::MimoProcessor<Derived
63
, APF_MIMOPROCESSOR_INTERFACE_POLICY
64
, APF_MIMOPROCESSOR_THREAD_POLICY
68
using _base = apf::MimoProcessor<Derived, APF_MIMOPROCESSOR_INTERFACE_POLICY
69
, APF_MIMOPROCESSOR_THREAD_POLICY, SSR_QUERY_POLICY>;
72
using rtlist_t = typename _base::rtlist_t;
73
using Input = typename _base::DefaultInput;
74
using ScopedLock = typename _base::ScopedLock;
75
using sample_type = typename _base::sample_type;
80
// TODO: try to remove this:
81
using SourceBase = Source;
86
State(apf::CommandQueue& fifo)
87
: reference_position(fifo)
88
, reference_orientation(fifo, Orientation(90))
89
, reference_offset_position(fifo)
90
, reference_offset_orientation(fifo)
91
, master_volume(fifo, 1)
92
, processing(fifo, true)
93
, amplitude_reference_distance(fifo, 3)
96
apf::SharedData<Position> reference_position;
97
apf::SharedData<Orientation> reference_orientation;
98
apf::SharedData<Position> reference_offset_position;
99
apf::SharedData<Orientation> reference_offset_orientation;
100
apf::SharedData<sample_type> master_volume;
101
apf::SharedData<bool> processing;
102
apf::SharedData<sample_type> amplitude_reference_distance;
105
// If you don't need a list proxy, just use a reference to the list
106
template<typename L, typename ListProxy, typename DataMember>
107
class AddToSublistCommand : public apf::CommandQueue::Command
110
AddToSublistCommand(L input, ListProxy output, DataMember member)
116
virtual void execute()
118
apf::distribute_list(_input, _output, _member);
121
// Empty function, because no cleanup is necessary.
122
virtual void cleanup() {}
130
template<typename L, typename ListProxy, typename DataMember>
131
void add_to_sublist(const L& input, ListProxy output, DataMember member)
133
_fifo.push(new AddToSublistCommand<L, ListProxy, DataMember>(
134
input, output, member));
137
template<typename L, typename ListProxy, typename DataMember>
138
class RemFromSublistCommand : public apf::CommandQueue::Command
141
RemFromSublistCommand(const L input, ListProxy output
148
virtual void execute()
150
apf::undistribute_list(_input, _output, _member, _garbage);
153
virtual void cleanup()
155
// Nothing to be done. _garbage is taken care of in the destructor.
162
typename std::remove_const<L>::type _garbage;
165
template<typename L, typename ListProxy, typename DataMember>
166
void rem_from_sublist(const L& input, ListProxy output, DataMember member)
168
_fifo.push(new RemFromSublistCommand<L, ListProxy, DataMember>(
169
input, output, member));
172
int add_source(const apf::parameter_map& p = apf::parameter_map());
173
void rem_source(id_t id);
174
void rem_all_sources();
176
Source* get_source(int id);
178
// May only be used in realtime thread!
179
const rtlist_t& get_source_list() const { return _source_list; }
181
bool show_head() const { return _show_head; }
183
// TODO: proper solution for getting the reproduction setup
184
template<typename SomeListType>
185
void get_loudspeakers(SomeListType&) {}
187
std::unique_ptr<ScopedLock> get_scoped_lock()
189
// TODO: in C++14, use make_unique()
190
return std::unique_ptr<ScopedLock>(new ScopedLock(_lock));
193
const sample_type master_volume_correction; // linear
196
RendererBase(const apf::parameter_map& p);
198
// TODO: make private?
199
sample_type _master_level;
201
rtlist_t _source_list;
203
// TODO: find a better solution to get loudspeaker vs. headphone renderer
207
apf::parameter_map _add_params(const apf::parameter_map& params)
210
temp.set("name", params.get("name", Derived::name()));
216
std::map<int, Source*> _source_map;
220
typename _base::Lock _lock;
224
* @param p Parameters for RendererBase and MimoProcessor
226
template<typename Derived>
227
RendererBase<Derived>::RendererBase(const apf::parameter_map& p)
228
: _base(_add_params(p))
230
, master_volume_correction(apf::math::dB2linear(
231
this->params.get("master_volume_correction", 0.0)))
233
, _source_list(_fifo)
238
/** Create a new source.
239
* @return ID of new source
240
* @throw unknown whatever the Derived::Source constructor throws
242
template<typename Derived>
243
int RendererBase<Derived>::add_source(const apf::parameter_map& p)
245
int id = _get_new_id();
247
typename Derived::Input::Params in_params;
249
in_params.set("id", in_params.get("id", id));
250
auto in = this->add(in_params);
252
// WARNING: if Derived::Input throws an exception, the SSR crashes!
254
typename Derived::Source::Params src_params;
256
src_params.parent = &this->derived();
257
src_params.fifo = &_fifo;
258
src_params.input = in;
260
// For now, Input ID and Source ID are the same:
263
typename Derived::Source* src;
267
src = _source_list.add(new typename Derived::Source(src_params));
271
// TODO: really remove the corresponding Input?
276
// This cannot be done in the Derived::Source constructor because then the
277
// connections to the Outputs are active before the Source is properly added
278
// to the source list:
281
_source_map[id] = src;
283
// TODO: what happens on failure? can there be failure?
286
template<typename Derived>
287
void RendererBase<Derived>::rem_source(id_t id)
289
auto delinquent = _source_map.find(id);
291
if (delinquent == _source_map.end())
297
auto* source = delinquent->second;
299
_source_map.erase(delinquent);
302
source->derived().disconnect();
304
auto input = const_cast<Input*>(&source->_input);
305
_source_list.rem(source);
307
// TODO: really remove the corresponding Input?
308
// ATTENTION: there may be several sources using the input! (or not?)
313
template<typename Derived>
314
void RendererBase<Derived>::rem_all_sources()
316
while (!_source_map.empty())
318
this->rem_source(_source_map.begin()->first);
323
template<typename Derived>
324
typename RendererBase<Derived>::Source*
325
RendererBase<Derived>::get_source(int id)
327
return maptools::get_item(_source_map, id);
330
template<typename Derived>
332
RendererBase<Derived>::_get_new_id()
334
return ++_highest_id;
338
template<typename Derived>
339
class RendererBase<Derived>::Source
340
: public _base::template ProcessItem<typename Derived::Source>
341
, public apf::has_begin_and_end<typename Input::iterator>
345
= typename _base::template ProcessItem<typename Derived::Source>;
349
= typename std::iterator_traits<typename Input::iterator>::value_type;
351
friend class RendererBase<Derived>; // rem_source() needs access to _input
353
struct Params : apf::parameter_map
355
Derived* parent = nullptr;
356
const typename Derived::Input* input = nullptr;
357
apf::CommandQueue* fifo = nullptr;
360
using apf::parameter_map::operator=;
363
explicit Source(const Params& p)
364
: parent(*(p.parent ? p.parent : throw std::logic_error(
365
"Bug (RendererBase::Source): parent == NULL!")))
366
, position(*(p.fifo ? p.fifo : throw std::logic_error(
367
"Bug (RendererBase::Source): fifo == NULL!")))
368
, orientation(*p.fifo)
369
, gain(*p.fifo, sample_type(1.0))
370
, mute(*p.fifo, false)
371
, model(*p.fifo, ::Source::point)
374
, _input(*(p.input ? p.input : throw std::logic_error(
375
"Bug (RendererBase::Source): input == NULL!")))
380
APF_PROCESS(Source, SourceBase)
382
this->_begin = _input.begin();
383
this->_end = _input.end();
385
if (!_input.parent.state.processing || this->mute)
387
this->weighting_factor = 0.0;
391
this->weighting_factor = this->gain;
392
// If the renderer does something nonlinear, the master volume should
393
// be applied to the output signal ... TODO: shall we care?
394
this->weighting_factor *= _input.parent.state.master_volume;
395
this->weighting_factor *= _input.parent.master_volume_correction;
398
_level_helper(_input.parent);
400
assert(this->weighting_factor.exactly_one_assignment());
403
sample_type get_level() const { return _level; }
405
// In the default case, the output level are ignored
406
bool get_output_levels(sample_type*, sample_type*) const { return false; }
413
apf::SharedData<Position> position;
414
apf::SharedData<Orientation> orientation;
415
apf::SharedData<sample_type> gain;
416
apf::SharedData<bool> mute;
417
apf::SharedData< ::Source::model_t> model;
419
apf::BlockParameter<sample_type> weighting_factor;
427
void _level_helper(apf::enable_queries&)
429
_pre_fader_level = apf::math::max_amplitude(_input.begin(), _input.end());
430
_level = _pre_fader_level * this->weighting_factor;
433
void _level_helper(apf::disable_queries&) {}
435
sample_type _pre_fader_level;
439
template<typename Derived>
440
class RendererBase<Derived>::Output : public _base::Output
443
Output(const typename _base::Output::Params& p)
448
struct Process : _base::Output::Process
450
explicit Process(Output& o) : _base::Output::Process(o) , _out(o) {}
454
_out._level_helper(_out.parent);
461
sample_type get_level() const { return _level; }
464
void _level_helper(apf::enable_queries&)
466
_level = apf::math::max_amplitude(this->buffer.begin()
467
, this->buffer.end());
470
void _level_helper(apf::disable_queries&) {}
476
// This is a kind of C++ mixin class, but it also includes the CRTP
477
template<typename Derived, template<typename> class Base>
478
struct SourceToOutput : Base<Derived>
480
using Input = typename Base<Derived>::Input;
482
struct Source : Base<Derived>::Source
484
using Params = typename Base<Derived>::Source::Params;
485
using sourcechannels_t = apf::fixed_vector<typename Derived::SourceChannel>;
487
template<typename... Args>
488
Source(const Params& p, Args&&... args)
489
: Base<Derived>::Source(p)
490
, sourcechannels(std::forward<Args>(args)...)
495
auto temp = std::list<typename Derived::SourceChannel*>();
496
apf::append_pointers(this->sourcechannels, temp);
497
this->parent.add_to_sublist(temp, apf::make_cast_proxy<Output>(
498
const_cast<rtlist_t&>(this->parent.get_output_list()))
499
, &Output::sourcechannels);
504
auto temp = std::list<typename Derived::SourceChannel*>();
505
apf::append_pointers(this->sourcechannels, temp);
506
this->parent.rem_from_sublist(temp, apf::make_cast_proxy<Output>(
507
const_cast<rtlist_t&>(this->parent.get_output_list()))
508
, &Output::sourcechannels);
511
sourcechannels_t sourcechannels;
514
using rtlist_t = typename Derived::rtlist_t;
517
struct Output : Base<Derived>::Output
519
using Params = typename Base<Derived>::Output::Params;
520
using sourcechannels_t = std::list<typename Derived::SourceChannel*>;
522
Output(const Params& p) : Base<Derived>::Output(p) {}
524
sourcechannels_t sourcechannels;
527
explicit SourceToOutput(const apf::parameter_map& params)
528
: Base<Derived>(params)
536
// Settings for Vim (http://www.vim.org/), please do not remove:
537
// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent
538
// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'='