mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
Session size is the number of elements, use CountVisitor to count the total number of sources inside (recursively through SessionSources).
878 lines
24 KiB
C++
878 lines
24 KiB
C++
/*
|
|
* This file is part of vimix - video live mixer
|
|
*
|
|
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU 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 <https://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
#include <algorithm>
|
|
|
|
#include "defines.h"
|
|
#include "BaseToolkit.h"
|
|
#include "Source.h"
|
|
#include "Settings.h"
|
|
#include "FrameBuffer.h"
|
|
#include "FrameGrabber.h"
|
|
#include "SessionCreator.h"
|
|
#include "SessionVisitor.h"
|
|
#include "ActionManager.h"
|
|
#include "SessionSource.h"
|
|
#include "RenderSource.h"
|
|
#include "MixingGroup.h"
|
|
#include "ControlManager.h"
|
|
#include "SourceCallback.h"
|
|
#include "CountVisitor.h"
|
|
#include "Log.h"
|
|
|
|
#include "Session.h"
|
|
|
|
SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_string(BaseToolkit::uniqueId())),
|
|
text(t), large(l), stick(s), pos(glm::vec2(520.f, 30.f)), size(glm::vec2(220.f, 220.f))
|
|
{
|
|
}
|
|
|
|
Session::Session(uint64_t id) : id_(id), active_(true), activation_threshold_(MIXING_MIN_THRESHOLD),
|
|
filename_(""), failedSource_(nullptr), thumbnail_(nullptr)
|
|
{
|
|
// create unique id
|
|
if (id_ == 0)
|
|
id_ = BaseToolkit::uniqueId();
|
|
|
|
config_[View::RENDERING] = new Group;
|
|
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
|
|
|
|
config_[View::GEOMETRY] = new Group;
|
|
config_[View::GEOMETRY]->scale_ = Settings::application.views[View::GEOMETRY].default_scale;
|
|
config_[View::GEOMETRY]->translation_ = Settings::application.views[View::GEOMETRY].default_translation;
|
|
|
|
config_[View::LAYER] = new Group;
|
|
config_[View::LAYER]->scale_ = Settings::application.views[View::LAYER].default_scale;
|
|
config_[View::LAYER]->translation_ = Settings::application.views[View::LAYER].default_translation;
|
|
|
|
config_[View::MIXING] = new Group;
|
|
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
|
|
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
|
|
|
|
config_[View::TEXTURE] = new Group;
|
|
config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale;
|
|
config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation;
|
|
|
|
input_sync_.resize(INPUT_MAX, Metronome::SYNC_NONE);
|
|
|
|
snapshots_.xmlDoc_ = new tinyxml2::XMLDocument;
|
|
start_time_ = gst_util_get_timestamp ();
|
|
}
|
|
|
|
|
|
Session::~Session()
|
|
{
|
|
// TODO delete all mixing groups?
|
|
auto group_iter = mixing_groups_.begin();
|
|
while ( group_iter != mixing_groups_.end() ){
|
|
delete (*group_iter);
|
|
group_iter = mixing_groups_.erase(group_iter);
|
|
}
|
|
|
|
// delete all sources
|
|
for(auto it = sources_.begin(); it != sources_.end(); ) {
|
|
// erase this source from the list
|
|
it = deleteSource(*it);
|
|
}
|
|
|
|
// delete all callbacks
|
|
for (auto iter = input_callbacks_.begin(); iter != input_callbacks_.end();
|
|
iter = input_callbacks_.erase(iter)) {
|
|
if ( iter->second.model_ != nullptr)
|
|
delete iter->second.model_;
|
|
if ( iter->second.reverse_ != nullptr)
|
|
delete iter->second.reverse_;
|
|
}
|
|
|
|
delete config_[View::RENDERING];
|
|
delete config_[View::GEOMETRY];
|
|
delete config_[View::LAYER];
|
|
delete config_[View::MIXING];
|
|
delete config_[View::TEXTURE];
|
|
|
|
snapshots_.keys_.clear();
|
|
delete snapshots_.xmlDoc_;
|
|
}
|
|
|
|
uint64_t Session::runtime() const
|
|
{
|
|
return gst_util_get_timestamp () - start_time_;
|
|
}
|
|
|
|
void Session::setActive (bool on)
|
|
{
|
|
if (active_ != on) {
|
|
active_ = on;
|
|
for(auto it = sources_.begin(); it != sources_.end(); ++it) {
|
|
(*it)->setActive(active_);
|
|
}
|
|
}
|
|
}
|
|
|
|
// update all sources
|
|
void Session::update(float dt)
|
|
{
|
|
// no update until render view is initialized
|
|
if ( render_.frame() == nullptr )
|
|
return;
|
|
|
|
// listen to inputs
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); ++k)
|
|
{
|
|
// get if the input is activated (e.g. key pressed)
|
|
bool input_active = Control::manager().inputActive(k->first);
|
|
|
|
// get the callback model for each input
|
|
if ( k->second.model_ != nullptr) {
|
|
|
|
// if the value referenced as pressed changed state
|
|
// or repeat key if there is no reverse callback
|
|
if ( input_active != k->second.active_ || k->second.reverse_ == nullptr) {
|
|
|
|
// ON PRESS
|
|
if (input_active) {
|
|
// delete the reverse if was not released
|
|
if (k->second.reverse_ != nullptr)
|
|
delete k->second.reverse_;
|
|
|
|
// generate a new callback from the model
|
|
SourceCallback *C = k->second.model_->clone();
|
|
// apply value multiplyer from input
|
|
C->multiply( Control::manager().inputValue(k->first) );
|
|
// add delay
|
|
C->delay( Metronome::manager().timeToSync( (Metronome::Synchronicity) input_sync_[k->first] ) );
|
|
// add callback to the source (force override)
|
|
k->second.source_->call( C, true );
|
|
// get the reverse if the callback, and remember it (can be null)
|
|
k->second.reverse_ = C->reverse(k->second.source_);
|
|
}
|
|
// ON RELEASE
|
|
else {
|
|
// call the reverse (NB: invalid value tested in call)
|
|
k->second.source_->call( k->second.reverse_, true );
|
|
// do not keep reference to reverse: will be deleted when terminated
|
|
k->second.reverse_ = nullptr;
|
|
}
|
|
|
|
// remember state of callback
|
|
k->second.active_ = input_active;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pre-render all sources
|
|
failedSource_ = nullptr;
|
|
bool ready = true;
|
|
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){
|
|
|
|
// ensure the RenderSource is rendering *this* session
|
|
RenderSource *rs = dynamic_cast<RenderSource *>( *it );
|
|
if ( rs!= nullptr && rs->session() != this )
|
|
rs->setSession(this);
|
|
|
|
// discard failed source
|
|
if ( (*it)->failed() ) {
|
|
failedSource_ = (*it);
|
|
}
|
|
// render normally
|
|
else {
|
|
if ( !(*it)->ready() )
|
|
ready = false;
|
|
// set inputs
|
|
|
|
// update the source
|
|
(*it)->setActive(activation_threshold_);
|
|
(*it)->update(dt);
|
|
// render the source
|
|
(*it)->render();
|
|
}
|
|
}
|
|
|
|
// update session's mixing groups
|
|
auto group_iter = mixing_groups_.begin();
|
|
while ( group_iter != mixing_groups_.end() ){
|
|
// update all valid groups
|
|
if ((*group_iter)->size() > 1) {
|
|
(*group_iter)->update(dt);
|
|
group_iter++;
|
|
}
|
|
else
|
|
// delete invalid groups (singletons)
|
|
group_iter = deleteMixingGroup(group_iter);
|
|
}
|
|
|
|
// update fading requested
|
|
if (fading_.active) {
|
|
|
|
// animate
|
|
fading_.progress += dt;
|
|
|
|
// update animation
|
|
if ( fading_.duration > 0.f && fading_.progress < fading_.duration ) {
|
|
// interpolation
|
|
float f = fading_.progress / fading_.duration;
|
|
f = ( 1.f - f ) * fading_.start + f * fading_.target;
|
|
render_.setFading( f );
|
|
}
|
|
// arrived at target
|
|
else {
|
|
// set precise value
|
|
render_.setFading( fading_.target );
|
|
// fading finished
|
|
fading_.active = false;
|
|
fading_.start = fading_.target;
|
|
fading_.duration = fading_.progress = 0.f;
|
|
}
|
|
}
|
|
|
|
// update the scene tree
|
|
render_.update(dt);
|
|
|
|
// draw render view in Frame Buffer
|
|
render_.draw();
|
|
|
|
// draw the thumbnail only after all sources are ready
|
|
if (ready)
|
|
render_.drawThumbnail();
|
|
}
|
|
|
|
SourceList::iterator Session::addSource(Source *s)
|
|
{
|
|
// lock before change
|
|
access_.lock();
|
|
|
|
// find the source
|
|
SourceList::iterator its = find(s);
|
|
|
|
// ok, it's NOT in the list !
|
|
if (its == sources_.end()) {
|
|
// insert the source in the rendering
|
|
render_.scene.ws()->attach(s->group(View::RENDERING));
|
|
// insert the source to the end of the list
|
|
sources_.push_back(s);
|
|
// return the iterator to the source created at the end
|
|
its = --sources_.end();
|
|
}
|
|
|
|
// unlock access
|
|
access_.unlock();
|
|
|
|
return its;
|
|
}
|
|
|
|
SourceList::iterator Session::deleteSource(Source *s)
|
|
{
|
|
// lock before change
|
|
access_.lock();
|
|
|
|
// find the source
|
|
SourceList::iterator its = find(s);
|
|
// ok, its in the list !
|
|
if (its != sources_.end()) {
|
|
// remove Node from the rendering scene
|
|
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
|
// inform group
|
|
if (s->mixingGroup() != nullptr)
|
|
s->mixingGroup()->detach(s);
|
|
// erase the source from the update list & get next element
|
|
its = sources_.erase(its);
|
|
// delete the source : safe now
|
|
delete s;
|
|
}
|
|
|
|
// unlock access
|
|
access_.unlock();
|
|
|
|
// return end of next element
|
|
return its;
|
|
}
|
|
|
|
void Session::removeSource(Source *s)
|
|
{
|
|
// lock before change
|
|
access_.lock();
|
|
|
|
// find the source
|
|
SourceList::iterator its = find(s);
|
|
// ok, its in the list !
|
|
if (its != sources_.end()) {
|
|
// remove Node from the rendering scene
|
|
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
|
// inform group
|
|
if (s->mixingGroup() != nullptr)
|
|
s->mixingGroup()->detach(s);
|
|
// erase the source from the update list & get next element
|
|
sources_.erase(its);
|
|
}
|
|
|
|
// unlock access
|
|
access_.unlock();
|
|
}
|
|
|
|
Source *Session::popSource()
|
|
{
|
|
Source *s = nullptr;
|
|
|
|
SourceList::iterator its = sources_.begin();
|
|
if (its != sources_.end())
|
|
{
|
|
s = *its;
|
|
// remove Node from the rendering scene
|
|
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
|
// inform group
|
|
if (s->mixingGroup() != nullptr)
|
|
s->mixingGroup()->detach(s);
|
|
// erase the source from the update list & get next element
|
|
sources_.erase(its);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static void replaceThumbnail(Session *s)
|
|
{
|
|
if (s != nullptr) {
|
|
FrameBufferImage *t = s->renderThumbnail();
|
|
if (t != nullptr) // avoid recursive infinite loop
|
|
s->setThumbnail(t);
|
|
}
|
|
}
|
|
|
|
void Session::setThumbnail(FrameBufferImage *t)
|
|
{
|
|
resetThumbnail();
|
|
// replace with given image
|
|
if (t != nullptr)
|
|
thumbnail_ = t;
|
|
// no thumbnail image given: capture from rendering in a parallel thread
|
|
else
|
|
std::thread( replaceThumbnail, this ).detach();
|
|
}
|
|
|
|
void Session::resetThumbnail()
|
|
{
|
|
if (thumbnail_ != nullptr)
|
|
delete thumbnail_;
|
|
thumbnail_ = nullptr;
|
|
}
|
|
|
|
void Session::setResolution(glm::vec3 resolution, bool useAlpha)
|
|
{
|
|
// setup the render view: if not specified the default config resulution will be used
|
|
render_.setResolution( resolution, useAlpha );
|
|
// store the actual resolution set in the render view
|
|
config_[View::RENDERING]->scale_ = render_.resolution();
|
|
}
|
|
|
|
void Session::setFadingTarget(float f, float duration)
|
|
{
|
|
// targetted fading value
|
|
fading_.target = CLAMP(f, 0.f, 1.f);
|
|
// starting point for interpolation
|
|
fading_.start = fading();
|
|
// initiate animation
|
|
fading_.progress = 0.f;
|
|
fading_.duration = duration;
|
|
// activate update
|
|
fading_.active = true;
|
|
}
|
|
|
|
SourceList::iterator Session::begin()
|
|
{
|
|
return sources_.begin();
|
|
}
|
|
|
|
SourceList::iterator Session::end()
|
|
{
|
|
return sources_.end();
|
|
}
|
|
|
|
SourceList::iterator Session::find(Source *s)
|
|
{
|
|
return std::find(sources_.begin(), sources_.end(), s);
|
|
}
|
|
|
|
SourceList::iterator Session::find(uint64_t id)
|
|
{
|
|
return std::find_if(sources_.begin(), sources_.end(), Source::hasId(id));
|
|
}
|
|
|
|
SourceList::iterator Session::find(std::string namesource)
|
|
{
|
|
return std::find_if(sources_.begin(), sources_.end(), Source::hasName(namesource));
|
|
}
|
|
|
|
SourceList::iterator Session::find(Node *node)
|
|
{
|
|
return std::find_if(sources_.begin(), sources_.end(), Source::hasNode(node));
|
|
}
|
|
|
|
SourceList::iterator Session::find(float depth_from, float depth_to)
|
|
{
|
|
return std::find_if(sources_.begin(), sources_.end(), Source::hasDepth(depth_from, depth_to));
|
|
}
|
|
|
|
SourceList Session::getDepthSortedList() const
|
|
{
|
|
return depth_sorted(sources_);
|
|
}
|
|
|
|
uint Session::size() const
|
|
{
|
|
return sources_.size();
|
|
}
|
|
|
|
uint Session::numSources() const
|
|
{
|
|
CountVisitor counter;
|
|
for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) {
|
|
(*it)->accept(counter);
|
|
}
|
|
return counter.numSources();
|
|
}
|
|
|
|
|
|
SourceIdList Session::getIdList() const
|
|
{
|
|
return ids(sources_);
|
|
}
|
|
|
|
std::list<std::string> Session::getNameList(uint64_t exceptid) const
|
|
{
|
|
std::list<std::string> namelist;
|
|
|
|
for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) {
|
|
if ( (*it)->id() != exceptid )
|
|
namelist.push_back( (*it)->name() );
|
|
}
|
|
|
|
return namelist;
|
|
}
|
|
|
|
|
|
bool Session::empty() const
|
|
{
|
|
return sources_.empty();
|
|
}
|
|
|
|
SourceList::iterator Session::at(int index)
|
|
{
|
|
if ( index < 0 || index > (int) sources_.size())
|
|
return sources_.end();
|
|
|
|
int i = 0;
|
|
SourceList::iterator it = sources_.begin();
|
|
while ( i < index && it != sources_.end() ){
|
|
i++;
|
|
++it;
|
|
}
|
|
return it;
|
|
}
|
|
|
|
int Session::index(SourceList::iterator it) const
|
|
{
|
|
int index = -1;
|
|
int count = 0;
|
|
for(auto i = sources_.begin(); i != sources_.end(); ++i, ++count) {
|
|
if ( i == it ) {
|
|
index = count;
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
void Session::move(int current_index, int target_index)
|
|
{
|
|
if ( current_index < 0 || current_index > (int) sources_.size()
|
|
|| target_index < 0 || target_index > (int) sources_.size()
|
|
|| target_index == current_index )
|
|
return;
|
|
|
|
SourceList::iterator from = at(current_index);
|
|
SourceList::iterator to = at(target_index);
|
|
if ( target_index > current_index )
|
|
++to;
|
|
|
|
Source *s = (*from);
|
|
sources_.erase(from);
|
|
sources_.insert(to, s);
|
|
}
|
|
|
|
bool Session::canlink (SourceList sources)
|
|
{
|
|
bool canlink = true;
|
|
|
|
// verify that all sources given are valid in the sesion
|
|
validate(sources);
|
|
|
|
for (auto it = sources.begin(); it != sources.end(); ++it) {
|
|
// this source is linked
|
|
if ( (*it)->mixingGroup() != nullptr ) {
|
|
// askt its group to detach it
|
|
canlink = false;
|
|
}
|
|
}
|
|
|
|
return canlink;
|
|
}
|
|
|
|
void Session::link(SourceList sources, Group *parent)
|
|
{
|
|
// we need at least 2 sources to make a group
|
|
if (sources.size() > 1) {
|
|
|
|
unlink(sources);
|
|
|
|
// create and add a new mixing group
|
|
MixingGroup *g = new MixingGroup(sources);
|
|
mixing_groups_.push_back(g);
|
|
|
|
// if provided, attach the group to the parent
|
|
if (g && parent != nullptr)
|
|
g->attachTo( parent );
|
|
|
|
}
|
|
}
|
|
|
|
void Session::unlink (SourceList sources)
|
|
{
|
|
// verify that all sources given are valid in the sesion
|
|
validate(sources);
|
|
|
|
// brute force : detach all given sources
|
|
for (auto it = sources.begin(); it != sources.end(); ++it) {
|
|
// this source is linked
|
|
if ( (*it)->mixingGroup() != nullptr ) {
|
|
// askt its group to detach it
|
|
(*it)->mixingGroup()->detach(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Session::addNote(SessionNote note)
|
|
{
|
|
notes_.push_back( note );
|
|
}
|
|
|
|
std::list<SessionNote>::iterator Session::beginNotes ()
|
|
{
|
|
return notes_.begin();
|
|
}
|
|
|
|
std::list<SessionNote>::iterator Session::endNotes ()
|
|
{
|
|
return notes_.end();
|
|
}
|
|
|
|
std::list<SessionNote>::iterator Session::deleteNote (std::list<SessionNote>::iterator n)
|
|
{
|
|
if (n != notes_.end())
|
|
return notes_.erase(n);
|
|
|
|
return notes_.end();
|
|
}
|
|
|
|
std::list<SourceList> Session::getMixingGroups () const
|
|
{
|
|
std::list<SourceList> lmg;
|
|
|
|
for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); ++group_it)
|
|
lmg.push_back( (*group_it)->getCopy() );
|
|
|
|
return lmg;
|
|
}
|
|
|
|
std::list<MixingGroup *>::iterator Session::deleteMixingGroup (std::list<MixingGroup *>::iterator g)
|
|
{
|
|
if (g != mixing_groups_.end()) {
|
|
delete (*g);
|
|
return mixing_groups_.erase(g);
|
|
}
|
|
return mixing_groups_.end();
|
|
}
|
|
|
|
std::list<MixingGroup *>::iterator Session::beginMixingGroup()
|
|
{
|
|
return mixing_groups_.begin();
|
|
}
|
|
|
|
std::list<MixingGroup *>::iterator Session::endMixingGroup()
|
|
{
|
|
return mixing_groups_.end();
|
|
}
|
|
|
|
|
|
size_t Session::numPlayGroups() const
|
|
{
|
|
return play_groups_.size();
|
|
}
|
|
|
|
void Session::addPlayGroup(const SourceIdList &ids)
|
|
{
|
|
play_groups_.push_back( ids );
|
|
}
|
|
|
|
void Session::addToPlayGroup(size_t i, Source *s)
|
|
{
|
|
if (i < play_groups_.size() )
|
|
{
|
|
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() )
|
|
play_groups_[i].push_back(s->id());
|
|
}
|
|
}
|
|
|
|
void Session::removeFromPlayGroup(size_t i, Source *s)
|
|
{
|
|
if (i < play_groups_.size() )
|
|
{
|
|
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() )
|
|
play_groups_[i].remove( s->id() );
|
|
}
|
|
}
|
|
|
|
void Session::deletePlayGroup(size_t i)
|
|
{
|
|
if (i < play_groups_.size() )
|
|
play_groups_.erase( play_groups_.begin() + i);
|
|
}
|
|
|
|
SourceList Session::playGroup(size_t i) const
|
|
{
|
|
SourceList list;
|
|
|
|
if (i < play_groups_.size() )
|
|
{
|
|
for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){
|
|
|
|
SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));;
|
|
if ( it != sources_.end())
|
|
list.push_back( *it);
|
|
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void Session::validate (SourceList &sources)
|
|
{
|
|
// verify that all sources given are valid in the sesion
|
|
// and remove the invalid sources
|
|
for (auto _it = sources.begin(); _it != sources.end(); ) {
|
|
SourceList::iterator found = std::find(sources_.begin(), sources_.end(), *_it);
|
|
if ( found == sources_.end() )
|
|
_it = sources.erase(_it);
|
|
else
|
|
_it++;
|
|
}
|
|
}
|
|
|
|
Session *Session::load(const std::string& filename, uint level)
|
|
{
|
|
// create session
|
|
SessionCreator creator(level);
|
|
creator.load(filename);
|
|
|
|
// return created session
|
|
return creator.session();
|
|
}
|
|
|
|
std::string Session::save(const std::string& filename, Session *session, const std::string& snapshot_name)
|
|
{
|
|
std::string ret;
|
|
|
|
if (session) {
|
|
// lock access while saving
|
|
session->access_.lock();
|
|
|
|
// capture a snapshot of current version if requested (do not create thread)
|
|
if (!snapshot_name.empty())
|
|
Action::takeSnapshot(session, snapshot_name, false );
|
|
|
|
// save file to disk
|
|
if (SessionVisitor::saveSession(filename, session))
|
|
// return filename string on success
|
|
ret = filename;
|
|
|
|
// unlock access after saving
|
|
session->access_.unlock();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Session::assignSourceCallback(uint input, Source *source, SourceCallback *callback)
|
|
{
|
|
// find if this callback is already assigned
|
|
auto k = input_callbacks_.begin();
|
|
for (; k != input_callbacks_.end(); ++k)
|
|
{
|
|
// yes, then just change the source pointer
|
|
if ( k->second.model_ == callback) {
|
|
if (k->second.reverse_)
|
|
delete k->second.reverse_;
|
|
k->second.source_ = source;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if this callback is not assigned yet (looped until end)
|
|
if ( k == input_callbacks_.end() ) {
|
|
// create new entry
|
|
std::multimap<uint, InputSourceCallback>::iterator added = input_callbacks_.emplace(input, InputSourceCallback() );
|
|
added->second.model_ = callback;
|
|
added->second.source_ = source;
|
|
}
|
|
}
|
|
|
|
void Session::swapSourceCallback(uint from, uint to)
|
|
{
|
|
std::multimap<uint, InputSourceCallback> swapped_callbacks_;
|
|
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); ++k)
|
|
{
|
|
if ( k->first == from )
|
|
swapped_callbacks_.emplace( to, k->second);
|
|
else
|
|
swapped_callbacks_.emplace( k->first, k->second);
|
|
}
|
|
|
|
input_callbacks_.swap(swapped_callbacks_);
|
|
}
|
|
|
|
void Session::copySourceCallback(uint from, uint to)
|
|
{
|
|
if ( input_callbacks_.count(from) > 0 ) {
|
|
auto from_callbacks = getSourceCallbacks(from);
|
|
for (auto it = from_callbacks.cbegin(); it != from_callbacks.cend(); ++it){
|
|
assignSourceCallback(to, it->first, it->second->clone() );
|
|
}
|
|
}
|
|
}
|
|
|
|
std::list< std::pair<Source *, SourceCallback *> > Session::getSourceCallbacks(uint input)
|
|
{
|
|
std::list< std::pair<Source *, SourceCallback*> > ret;
|
|
|
|
if ( input_callbacks_.count(input) > 0 ) {
|
|
auto result = input_callbacks_.equal_range(input);
|
|
for (auto it = result.first; it != result.second; ++it)
|
|
ret.push_back( std::pair<Source *, SourceCallback*>(it->second.source_, it->second.model_) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Session::deleteSourceCallback(SourceCallback *callback)
|
|
{
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); ++k)
|
|
{
|
|
if ( k->second.model_ == callback) {
|
|
delete callback;
|
|
if (k->second.reverse_)
|
|
delete k->second.reverse_;
|
|
input_callbacks_.erase(k);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Session::deleteSourceCallbacks(uint input)
|
|
{
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end();)
|
|
{
|
|
if ( k->first == input) {
|
|
if (k->second.model_)
|
|
delete k->second.model_;
|
|
if (k->second.reverse_)
|
|
delete k->second.reverse_;
|
|
k = input_callbacks_.erase(k);
|
|
}
|
|
else
|
|
++k;
|
|
}
|
|
}
|
|
|
|
void Session::deleteSourceCallbacks(Source *source)
|
|
{
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end();)
|
|
{
|
|
if ( k->second.source_ == source) {
|
|
if (k->second.model_)
|
|
delete k->second.model_;
|
|
if (k->second.reverse_)
|
|
delete k->second.reverse_;
|
|
k = input_callbacks_.erase(k);
|
|
}
|
|
else
|
|
++k;
|
|
}
|
|
}
|
|
|
|
|
|
void Session::clearSourceCallbacks()
|
|
{
|
|
for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); )
|
|
{
|
|
if (k->second.model_)
|
|
delete k->second.model_;
|
|
if (k->second.reverse_)
|
|
delete k->second.reverse_;
|
|
|
|
k = input_callbacks_.erase(k);
|
|
}
|
|
}
|
|
|
|
std::list<uint> Session::assignedInputs()
|
|
{
|
|
std::list<uint> inputs;
|
|
|
|
// fill with list of keys
|
|
for(const auto& [key, value] : input_callbacks_) {
|
|
inputs.push_back(key);
|
|
}
|
|
|
|
// remove duplicates
|
|
inputs.unique();
|
|
|
|
return inputs;
|
|
}
|
|
|
|
bool Session::inputAssigned(uint input)
|
|
{
|
|
return input_callbacks_.find(input) != input_callbacks_.end();
|
|
}
|
|
|
|
void Session::setInputSynchrony(uint input, Metronome::Synchronicity sync)
|
|
{
|
|
input_sync_[input] = sync;
|
|
}
|
|
|
|
std::vector<Metronome::Synchronicity> Session::getInputSynchrony()
|
|
{
|
|
return input_sync_;
|
|
}
|
|
|
|
Metronome::Synchronicity Session::inputSynchrony(uint input)
|
|
{
|
|
return input_sync_[input];
|
|
}
|
|
|