From 1cb448c42e33dca8d05cc5cc525bf58fc012de8f Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sun, 26 Dec 2021 00:41:02 +0100 Subject: [PATCH] Output session fading fixed for OSC and animation. Linear interpolation (instead of dichotomy converge) for fading at Session update. Mixing View update reads value of session fading to animate the cursor (which was preventing other manipulation of fading). Cleanup fading in OSC controller, with animation options and fade-in and fade-out controls. --- ControlManager.cpp | 44 +++++++++++++++++++++++++------ ControlManager.h | 4 ++- ImGuiVisitor.cpp | 4 +-- Mixer.cpp | 2 +- MixingView.cpp | 52 +++++++++++++------------------------ RenderView.h | 9 +++++-- Session.cpp | 43 +++++++++++++++++++++++------- Session.h | 24 ++++++++++++++--- SessionCreator.cpp | 2 +- TransitionView.cpp | 4 +-- rsc/osc/vimix.mk1.touchosc | Bin 1851 -> 2071 bytes 11 files changed, 124 insertions(+), 64 deletions(-) diff --git a/ControlManager.cpp b/ControlManager.cpp index ca9d4db..91c1674 100644 --- a/ControlManager.cpp +++ b/ControlManager.cpp @@ -86,7 +86,8 @@ void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, Control::manager().sendOutputStatus(remoteEndpoint); } else - Control::manager().receiveOutputAttribute(attribute, m.ArgumentStream()); + if ( Control::manager().receiveOutputAttribute(attribute, m.ArgumentStream()) ) + Control::manager().sendOutputStatus(remoteEndpoint); } // ALL sources target: apply attribute to all sources of the session else if ( target.compare(OSC_ALL) == 0 ) @@ -268,11 +269,13 @@ void Control::terminate() } -void Control::receiveOutputAttribute(const std::string &attribute, +bool Control::receiveOutputAttribute(const std::string &attribute, osc::ReceivedMessageArgumentStream arguments) { + bool send_feedback = false; + try { - /// e.g. '/vimix/output/enable' or '/vimix/output/enable T' or '/vimix/output/enable F' + /// e.g. '/vimix/output/enable' or '/vimix/output/enable 1.0' or '/vimix/output/enable 0.0' if ( attribute.compare(OSC_OUTPUT_ENABLE) == 0) { float on = 1.f; if ( !arguments.Eos()) { @@ -280,7 +283,7 @@ void Control::receiveOutputAttribute(const std::string &attribute, } Settings::application.render.disabled = on < 0.5f; } - /// e.g. '/vimix/output/disable' or '/vimix/output/disable T' or '/vimix/output/disable F' + /// e.g. '/vimix/output/disable' or '/vimix/output/disable 1.0' or '/vimix/output/disable 0.0' else if ( attribute.compare(OSC_OUTPUT_DISABLE) == 0) { float on = 1.f; if ( !arguments.Eos()) { @@ -288,11 +291,34 @@ void Control::receiveOutputAttribute(const std::string &attribute, } Settings::application.render.disabled = on > 0.5f; } - /// e.g. '/vimix/output/fading f 0.2' + /// e.g. '/vimix/output/fading f 0.2' or '/vimix/output/fading ff 1.0 300.f' else if ( attribute.compare(OSC_OUTPUT_FADING) == 0) { - float fading = 0.f; - arguments >> fading >> osc::EndMessage; - Mixer::manager().session()->setFading(fading); // TODO move cursor when in Mixing view + float f = 0.f, d = 0.f; + // first argument is fading value + arguments >> f; + if (arguments.Eos()) + arguments >> osc::EndMessage; + // if a second argument is given, it is a duration + else + arguments >> d >> osc::EndMessage; + Mixer::manager().session()->setFadingTarget(f, d); + } + /// e.g. '/vimix/output/fadein' or '/vimix/output/fadein f 300.f' + else if ( attribute.compare(OSC_OUTPUT_FADE_IN) == 0) { + float f = 0.f; + // if argument is given, it is a duration + if (!arguments.Eos()) + arguments >> f >> osc::EndMessage; + Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() + f * 0.001); + send_feedback = true; + } + else if ( attribute.compare(OSC_OUTPUT_FADE_OUT) == 0) { + float f = 0.f; + // if argument is given, it is a duration + if (!arguments.Eos()) + arguments >> f >> osc::EndMessage; + Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() - f * 0.001); + send_feedback = true; } #ifdef CONTROL_DEBUG else { @@ -310,6 +336,8 @@ void Control::receiveOutputAttribute(const std::string &attribute, catch (osc::WrongArgumentTypeException &e) { Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'output'", attribute.c_str()); } + + return send_feedback; } bool Control::receiveSourceAttribute(Source *target, const std::string &attribute, diff --git a/ControlManager.h b/ControlManager.h index d95f5da..72454b3 100644 --- a/ControlManager.h +++ b/ControlManager.h @@ -12,6 +12,8 @@ #define OSC_OUTPUT_ENABLE "/enable" #define OSC_OUTPUT_DISABLE "/disable" #define OSC_OUTPUT_FADING "/fading" +#define OSC_OUTPUT_FADE_IN "/fadein" +#define OSC_OUTPUT_FADE_OUT "/fadeout" #define OSC_ALL "/all" #define OSC_SELECTED "/selected" @@ -68,7 +70,7 @@ protected: std::string FullMessage( const osc::ReceivedMessage& m ); }; - void receiveOutputAttribute(const std::string &attribute, + bool receiveOutputAttribute(const std::string &attribute, osc::ReceivedMessageArgumentStream arguments); bool receiveSourceAttribute(Source *target, const std::string &attribute, diff --git a/ImGuiVisitor.cpp b/ImGuiVisitor.cpp index a83108e..c9cfd0a 100644 --- a/ImGuiVisitor.cpp +++ b/ImGuiVisitor.cpp @@ -620,12 +620,12 @@ void ImGuiVisitor::visit (SessionFileSource& s) ImGui::SameLine(); ImGui::Text("Sources"); - if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFading(0.f); + if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFadingTarget(0.f); float f = s.session()->fading(); ImGui::SameLine(0, 10); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); if (ImGui::SliderFloat("Fading", &f, 0.0, 1.0, f < 0.001 ? "None" : "%.2f") ) - s.session()->setFading(f); + s.session()->setFadingTarget(f); if (ImGui::IsItemDeactivatedAfterEdit()){ std::ostringstream oss; oss << s.name() << ": Fading " << std::setprecision(2) << f; diff --git a/Mixer.cpp b/Mixer.cpp index 00bf7ad..ffc3ca3 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -1240,7 +1240,7 @@ void Mixer::swap() session_->setResolution( session_->config(View::RENDERING)->scale_ ); // transfer fading - session_->setFading( MAX(back_session_->fading(), session_->fading()), true ); + session_->setFadingTarget( MAX(back_session_->fadingTarget(), session_->fadingTarget())); // no current source current_source_ = session_->end(); diff --git a/MixingView.cpp b/MixingView.cpp index b15f5ec..a3f42aa 100644 --- a/MixingView.cpp +++ b/MixingView.cpp @@ -321,18 +321,8 @@ void MixingView::update(float dt) limbo_->scale_ = glm::vec3(p, p, 1.f); // - // Set slider to match the actual fading of the session - // - float f = Mixer::manager().session()->empty() ? 0.f : Mixer::manager().session()->fading(); - - // reverse calculate angle from fading & move slider - slider_root_->rotation_.z = SIGN(slider_root_->rotation_.z) * asin(f) * 2.f; - - // visual feedback on mixing circle - f = 1.f - f; - mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f); - // prevent invalid scaling + // float s = CLAMP(scene.root()->scale_.x, MIXING_MIN_SCALE, MIXING_MAX_SCALE); scene.root()->scale_.x = s; scene.root()->scale_.y = s; @@ -342,22 +332,16 @@ void MixingView::update(float dt) if (Mixer::manager().view() == this ){ // - // Set session fading to match the slider angle (during animation) + // Set slider to match the actual fading of the session // + float f = Mixer::manager().session()->fading(); - // calculate fading from angle - float f = sin( ABS(slider_root_->rotation_.z) * 0.5f); + // reverse calculate angle from fading & move slider + slider_root_->rotation_.z = SIGN(-slider_root_->rotation_.z) * asin(f) * -2.f; - // apply fading - if ( ABS_DIFF( f, Mixer::manager().session()->fading()) > EPSILON ) - { - // apply fading to session - Mixer::manager().session()->setFading(f); - - // visual feedback on mixing circle - f = 1.f - f; - mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f); - } + // visual feedback on mixing circle + f = 1.f - f; + mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f); // update the selection overlay updateSelectionOverlay(); @@ -374,18 +358,14 @@ std::pair MixingView::pick(glm::vec2 P) // deal with internal interactive objects if ( pick.first == button_white_ || pick.first == button_black_ ) { - RotateToCallback *anim = nullptr; - if (pick.first == button_white_) - anim = new RotateToCallback(0.f, 500.f); - else - anim = new RotateToCallback(SIGN(slider_root_->rotation_.z) * M_PI, 500.f); - // animate clic pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f)); - // reset & start animation - slider_root_->update_callbacks_.clear(); - slider_root_->update_callbacks_.push_back(anim); + // animated fading in session + if (pick.first == button_white_) + Mixer::manager().session()->setFadingTarget(0.f, 500.f); + else + Mixer::manager().session()->setFadingTarget(1.f, 500.f); } else if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) { @@ -470,10 +450,14 @@ View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pai // animate slider (rotation angle on its parent) slider_root_->rotation_.z = angle; + // calculate fading from angle + float f = sin( ABS(angle) * 0.5f); + Mixer::manager().session()->setFadingTarget(f); + // cursor feedback slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f ); std::ostringstream info; - info << "Output " << 100 - int(Mixer::manager().session()->fading() * 100.0) << " %"; + info << "Output " << 100 - int(f * 100.0) << " %"; return Cursor(Cursor_Hand, info.str() ); } else if (pick.first == limbo_slider_) { diff --git a/RenderView.h b/RenderView.h index 7a2a7f1..f7e5bf6 100644 --- a/RenderView.h +++ b/RenderView.h @@ -8,6 +8,8 @@ class RenderView : public View { + friend class Session; + // rendering FBO FrameBuffer *frame_buffer_; Surface *fading_overlay_; @@ -26,12 +28,15 @@ public: void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false); glm::vec3 resolution() const { return frame_buffer_->resolution(); } - void setFading(float f = 0.f); - float fading() const; // current frame inline FrameBuffer *frame () const { return frame_buffer_; } +protected: + + void setFading(float f = 0.f); + float fading() const; + // get a thumbnail outside of opengl context; wait for a promise to be fullfiled after draw void drawThumbnail(); FrameBufferImage *thumbnail (); diff --git a/Session.cpp b/Session.cpp index 4bbd30d..6591cb0 100644 --- a/Session.cpp +++ b/Session.cpp @@ -38,7 +38,7 @@ SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_str } Session::Session() : active_(true), activation_threshold_(MIXING_MIN_THRESHOLD), - filename_(""), failedSource_(nullptr), fading_target_(0.f), thumbnail_(nullptr) + filename_(""), failedSource_(nullptr), thumbnail_(nullptr) { config_[View::RENDERING] = new Group; config_[View::RENDERING]->scale_ = glm::vec3(0.f); @@ -150,10 +150,28 @@ void Session::update(float dt) group_iter = deleteMixingGroup(group_iter); } - // apply fading (smooth dicotomic reaching) - float f = render_.fading(); - if ( ABS_DIFF(f, fading_target_) > EPSILON) { - render_.setFading( f + ( fading_target_ - f ) / 2.f); + // 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 @@ -292,12 +310,17 @@ void Session::setResolution(glm::vec3 resolution, bool useAlpha) config_[View::RENDERING]->scale_ = render_.resolution(); } -void Session::setFading(float f, bool forcenow) +void Session::setFadingTarget(float f, float duration) { - if (forcenow) - render_.setFading( f ); - - fading_target_ = CLAMP(f, 0.f, 1.f); + // 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() diff --git a/Session.h b/Session.h index 93c18fc..91dd49a 100644 --- a/Session.h +++ b/Session.h @@ -98,8 +98,9 @@ public: void setResolution (glm::vec3 resolution, bool useAlpha = false); // manipulate fading of output - void setFading (float f, bool forcenow = false); - inline float fading () const { return fading_target_; } + void setFadingTarget (float f, float duration = 0.f); + inline float fadingTarget () const { return fading_.target; } + inline float fading () const { return render_.fading(); } // activation threshold for source (mixing distance) inline void setActivationThreshold(float t) { activation_threshold_ = t; } @@ -162,10 +163,27 @@ protected: std::map config_; SessionSnapshots snapshots_; std::vector play_groups_; - float fading_target_; std::mutex access_; FrameBufferImage *thumbnail_; uint64_t start_time_; + + struct Fading + { + bool active; + float start; + float target; + float duration; + float progress; + + Fading() { + active = false; + start = 0.f; + target = 0.f; + duration = 0.f; + progress = 0.f; + } + }; + Fading fading_; }; diff --git a/SessionCreator.cpp b/SessionCreator.cpp index 7f62f14..9896b9d 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -922,7 +922,7 @@ void SessionLoader::visit (SessionFileSource& s) // set fading float f = 0.f; xmlCurrent_->QueryFloatAttribute("fading", &f); - s.session()->setFading(f); + s.session()->setFadingTarget(f); // set uri XMLElement* pathNode = xmlCurrent_->FirstChildElement("path"); if (pathNode) { diff --git a/TransitionView.cpp b/TransitionView.cpp index 1f6721d..9b39a1d 100644 --- a/TransitionView.cpp +++ b/TransitionView.cpp @@ -134,7 +134,7 @@ void TransitionView::update(float dt) transition_source_->group(View::MIXING)->translation_.y = 0.f; // no fading when cross fading - Mixer::manager().session()->setFading( 0.f ); + Mixer::manager().session()->setFadingTarget( 0.f ); } // fade to black else @@ -151,7 +151,7 @@ void TransitionView::update(float dt) f = ( 2.f * d + 1.f); // quadratic f *= f; } - Mixer::manager().session()->setFading( 1.f - f ); + Mixer::manager().session()->setFadingTarget( 1.f - f ); } // request update diff --git a/rsc/osc/vimix.mk1.touchosc b/rsc/osc/vimix.mk1.touchosc index 33cd2962bdf79dfd2310fe61e2ea2e7df77d54a9..ec0801a960922b347a1bd0f6071793c417d099b0 100644 GIT binary patch delta 2024 zcmV@6aWYa2mtE?nvo43e@Ya`zlz2Concj|3Yl_d5|iA)T&Lpt zl45g78nAE!KmBd#W@#c3wTV6n7MS1v^y%)ibZ&kG4ydhde=+l1mEpdzOwD#p&ziXt zmFd3R|06R$Zf}4ddaGsTxdu~nJj+%YfvIg&MrPrAQ}{hE!wb)!+3r$bMvuaGmNQca zf6VR8Qa9%M#ICuz1K+e9u+#_BvC#*j-j`gawt$b>W0hfd6w%F6Wt82Gw=iGLh06R9 zM)TeCe|WGOIr7>Rz-qWrRi*}JukhiQWBfZgc z@9UZO_2hj$`@Wue|Mtf8+@dB&GoQKdOl=v?;p;#*Y_NuRb^x&koD6?tG27b6^oDM}Pke9XTFKvs!vkino#FY8 zf%0S{=l*>(cp491-Vqs(!8jR_)fI%c`-st2{v6l^BgE|;q3!#6*bg>(>rRbDe~op^ z_NPqEw-+#L`tXxqu>*P^8cmWL`kf zkPsx(6*Rnnpe`Y(L03@s0)je(pgLVaoeK!k2tg8ELD~fbJraUMx`G}rAn1+|B+wOf zcL707LJ&_^Q1ik{ck){OmdJXVM8|S`~iw zocA;F6tW8ZEYN;8NiyLSvI_hRlHHMH{3&EK{Vd+rqR)6lxj3Jd5S`S(eD|YoHx}m4v&l_AROs4V@(rB%q*;jz)BBee>>fb&@e_|iX4Fn zMRp>=P&svyzzO`htA1a4Gx)#2e8XmoqKeeXI9$PuKKCv;|FGYr?20cPT=9Y47n8XX z)uCoasIR!|i_3P)>fY(F5 ztDf3nDflf)-4<<|6fYZSAO)!?*1(0A%KI&G2$$4Q>iLGt$Epyr#A&04a<0O#?a*$S z;BNA`Y&Ez|D!56lrJie*(4}o}=+Jg@v~o4H6FRhc&uvsge@n-4+j+;WS3^t3Z=dsi zD^)_P(ec~oyx)q|(9-c+EiS{?bBzVcv~>J-AZQzcmam5PJij#ty@k~Rq8&G(IGh#8 z0*;w@ge!_EuK`w>?7380kcmbd7@!U2Acl5UU^xrIwCeL3#MsF%=?R~h9jA)P&--DoLm zBQvi`p&TwI*bNa-A(u>c+wYt0hsOM8-b8J}F*r z3r@V=7MyssEjaONTX5plw&298ZNZ6G+kz9Xwgo3?e|ei-OAAiY@;1A83oZ?5ao8oY z5ruOHNZP_Nvh9Ks75?P`~?DRHp1f??dwfNFM9R6St z0Sfx!Vo0n)`XGXg`Hq&b9Su6A+C@i5Kzf%Ye+BgtQE8b5IgY@kI=gS(Q1WB3x1pE>*VNnh9g;3djMxvwU`#B(K{#Q-E;5YI!ZX|TcIKJR-+@9;qo4sJ7^vs>OYFI`EQ^td84jIKWErz@BAtUWd4;g7&{QrJ97{G&w z=OxE}ddw*CFzUyQa5x|$G#(v*lCw?wjOTw=uss+IJUjtNzqSS%?feRBkO&PtKAK5a zh2iu>t646jIXXk!okY9CPQvqhRR|>lN8A50OE-6}f23rOBw6EluI5=>>9L#d=>MT_ z{|8V@2MF;3&xex-002Hc002-+0Rj{N6aWYa2mtE?np5!t&xex-002HclL!Yb2FeEj G00023|J(5Z delta 1824 zcmV+*2jBRY5W5ZyP)h>@6aWYa2mrKwnUM`2e@qmH|BA)^%&@9dg-kg!(Mi6+lv8oN zNwJA04OoZ*{`#-6*%~n}X-2LD3dr-GKHYu#ZF2WLus~yLxa*~3OBC~!rW%H=Ir`Ez zC2IK6{nDns-`@cxbT(e@z82N69Nmy8j%xT4)u!Qd3wWJv!vn`%8n&l+`&;2N-cnNl ze|3N7De6iwjfSmQ@JZbQo-$d?)iL0ev0zh;HN4H3OB5YZ>`y$25~CAmt-We%iTcCM zR?+=`Sg@H{?XAXv&D579ssWa-@aC8FUGQ9!DAQHK{V{x&;rB<5M9mejHufiKcA^xX zS8~rQ>GMkdc_sJ!{hj96p6h^E-OTE~f6pdQ^Xbbwqzfd9g)&0;7AA@i3|Uh|A&Y)A z2RN=otv2oo7)Rp3l*VEAzMYN(U+F!-zv6`}BLdP5cR@8=W4&_hHN4^)05--JzIka! z&0**%wgwx@aB~;#@4m%Cjw>>8MKoDO<_#2Sm?Dv^AngW%rkJ2MSwYhq2pVF7e_CV( z4R0W5fC*}n6*RbkAQ=-RkQF4~K+q#5h$k!P@dkn(FhLwyK@T?&)WHO?WCeBpnIN^- zUCze7Ae#~r)G8JWJW|xo6rsulaREg&z!shv0hIGRh~BbZ?hF(eRngu}*V)(^3asV8 z(l#zZ^NVQoHE5j*(E6PMns^0Sf8VcyY)~L;UqfbAK~~JP)-_~RBwD|iXw7TLs>rkF zVx9?CkjYi#*>f?^_-n{2$g@EH)xpWQYsf0dGf4J;ld;#3k<7DXTHF67Ey{55aLXgQ zea=M-Z6-IlE$#PgYGd+K8(qG578(8b4@UnfTGR30m1=4KYKjZwr0z}Ue-YtW*paNV zu)lC4TKhesiG^P8iS~>Qz=R!brsJn|8h#d!K0}|1{uzZu7JZ;v-KE+arqTBt(~Qa# z;J`P$I`jEOk&Tx~q8JC3-!rqq`GNgA78X-007+mY%Y#gBOI8Q12nW)kcydA%CEEBuyX3w~fV@Z62Xh6%URr;UeYXKITDj zJ0n=Tcv;tsaaa^A4&i*5h>NtJ6T`#liDIJG%H6#MENKdFX_sXdBU`__GuBC z9MaWUv$#4tTyLUCT-SQ2nmalnoJ#Z**DvDI)!=rB;HIm%W?>aambUMcp&b-x+ttvT zWN3?-+p30^Oyc&7e~H_yhL%j;J{R*=sDxG~lef>syydH*C6l*uV(>K!t4G4LWb$^x z$v#HQRzv%_yj3TowcZ1~k=Wb&Nh}({xOgGPB3z!&cvY~;6~15SuR?x_DV(TAELdbu zRv>}4Ho#j3VYZ%p4HE41N|?^e`KcS-R4DmeWBVF}mk5_}f8nVmL0b5Xds<9$J)u;1 zEVD}Gv$61|vVVd07F!-|e4UV%;)lgmPC1iT&U~y~?TK48*Q@ql6d3Ml_v4XG>wxku z-#)JwG3=>sH0zCgy=UUa$2M<#DC*h)8xJ!+P^d5WJ^1=s`+R+^eZIEVK3`jFpRcX8 z&)3%4=WA>2fAh7q_64~Zv$|OOf?SN*&8_{cppAWDOulO#Ou$xT= zXZgGrNI-cVqoQInpt@fO$LLbG(- z5Q}Y5x>1pq*2K332RL22ETRu~evab}(Ly=x(@d7b#PL{Bp(2L?&G#xl>hNZZh`z3=QSUXuq>DS z4?%GYu4D*X_4uPreR8A#Amd)iW15So-f#sMf&2 z%GxC{EjfICiCd<5&X71tMUkHT-r~xJi#C;`f5}Jk8kZbhu!Z-9^VfX(7P)i30+L(|Aw*~+J zL^c2bP)h*<6aW+e2nYxOw0)UV+L(|Aw*~+JL^c2b2><{9000000000000000003!j Ollli22JQv`0002LX?hU=