Files
vimix/Session.cpp
Bruno Herbelin 8fef0052a3 New CountVisitor to count the number of sources in session
Session size is the number of elements, use CountVisitor to count the total number of sources inside (recursively through SessionSources).
2022-04-09 00:35:20 +02:00

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];
}