From 0859cfbd505dbb34c38e4841e76a7300e6609a88 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Thu, 9 Feb 2023 18:20:27 +0100 Subject: [PATCH 1/5] VSE: Add retiming tool This tools allows to change strip playback speed by manipulating retiming handles. More handles can be added to single strip to create variable playback speed. This tool replaces Speed Factor property in time panel. --- .../datafiles/icons/ops.sequencer.retime.dat | Bin 0 -> 3950 bytes .../scripts/startup/bl_ui/space_sequencer.py | 6 - .../startup/bl_ui/space_toolsystem_toolbar.py | 13 + .../blenloader/intern/versioning_300.cc | 30 + .../blender/editors/datafiles/CMakeLists.txt | 1 + .../editors/space_sequencer/CMakeLists.txt | 3 + .../space_sequencer/sequencer_gizmo_retime.cc | 108 ++++ .../sequencer_gizmo_retime_type.cc | 568 ++++++++++++++++++ .../space_sequencer/sequencer_intern.h | 24 + .../editors/space_sequencer/sequencer_ops.c | 6 + .../space_sequencer/sequencer_retiming.cc | 377 ++++++++++++ .../editors/space_sequencer/space_sequencer.c | 9 +- source/blender/makesdna/DNA_sequence_types.h | 20 +- source/blender/makesrna/intern/rna_internal.h | 1 + .../blender/makesrna/intern/rna_sequencer.c | 160 +++-- .../makesrna/intern/rna_sequencer_api.c | 48 ++ source/blender/sequencer/CMakeLists.txt | 3 + source/blender/sequencer/SEQ_retiming.h | 43 ++ source/blender/sequencer/SEQ_retiming.hh | 15 + source/blender/sequencer/SEQ_time.h | 1 - source/blender/sequencer/intern/sequencer.c | 24 + source/blender/sequencer/intern/strip_edit.c | 8 +- .../sequencer/intern/strip_retiming.cc | 246 ++++++++ source/blender/sequencer/intern/strip_time.c | 59 +- source/blender/sequencer/intern/strip_time.h | 6 +- 25 files changed, 1694 insertions(+), 85 deletions(-) create mode 100644 release/datafiles/icons/ops.sequencer.retime.dat create mode 100644 source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc create mode 100644 source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc create mode 100644 source/blender/editors/space_sequencer/sequencer_retiming.cc create mode 100644 source/blender/sequencer/SEQ_retiming.h create mode 100644 source/blender/sequencer/SEQ_retiming.hh create mode 100644 source/blender/sequencer/intern/strip_retiming.cc diff --git a/release/datafiles/icons/ops.sequencer.retime.dat b/release/datafiles/icons/ops.sequencer.retime.dat new file mode 100644 index 0000000000000000000000000000000000000000..58075c0409b7de9be7f3e69bfadc427309e72e47 GIT binary patch literal 3950 zcmeH_Z%@-|7{>i7kdPp#F`Gm(aA9*B>Z~W80KNcs&_xpb7GS zH!xX(9YSLn6gQ^9&R_<_U}VTEaBut^p8N3|ocGI}=eqm!xv&2DkswG`uL8gG`=%p+)+miqI$bXRGqrJ;MA?ED`!P~LEp*pJ-&Eh!InNdE74OC2fzg4{9=Bu2F=Vk{Bd!rP%PrR} z*LCxdZOHcb`O$u;&ilRhdmnU6_DuGW?Ub1^Gi|JiHHmF;Q{0qne`+r|XkE7o_pS-oHZ=S=68htp? z2-f&U@K7+sL*e_}cH;ZoL8_WLNbTp}=l1homP&;$OWW#daa%3xZ(wQF*Cnm07_zPy z3p>xs3p?rh<4U^zWKY$UpZ8v_a5rL@?_mpdZ>@@+)+KH5#x68 zj!IPBh1*4^?#j6fnBL_jQ{I}eXRuyKVRGGbm|T~D$(;>ia$OUc+&K>>cX1e#>v3ap z7CR#5PPW*^bF&J21IICnmRY9+TTJV{#uX znA~SOCRcZ3a$kqPd1_Oiyr6V#3h41Qx)C(Q8h z%hGDGRM=ME0Kc;Sy7WfXsxoxNcvhD6g`LNhXXSKV*m_)fviCfXSc#G-Ir}{SJTJd` zu^_*abE!o+C+Aa((DUiV%wl@+CG7s3y@IK%>=il2&-h>5mA$ezJ(j(f9?O7c#{M14 zj%UX~|A7ws?7i%LJRy5u+?5)ri5iK<{892)GEf5ZW@+8qn{+w?nPZKbgJb^l$Z_a}+c@k)#MgZQ1 znt`V8)OP}U;yVsB$s_*}`NMzYZv=2PeaB>z{0aAe`c4B)$$)Q!??S4iA{DI)3hsh} zR?!Oc7HXL-3Q832MwJ=R86~DXD#nZPWxRubGSDjXR#2F?Y>`)Z1z*7*u1M-GzsYZs zo4n3zA>6f)&TsJR+&Wj{*4Q;xW7av1(GX}zqk#vtMrrgKRSIZyDX{L}fYM2DlDp(C zJmt?E(n8=5Z1^?$5;;f?@`L0xk_h2WgrLHMJP{_)P39)+=7{hO?m7=={3ZSp%-!(A zM34yG@Jrp26N{>J?5BkMxg{a6PyuH&cR-dIN7U=9q&*# z_y5*wZl2NA`Q5g(#vd@<;8QuNPGi++iMDQY%2 zJ3WgeW_Fs3FdU*7E{qTrM&W5hNo*{Fs91ypMO4HmvDhuKzkdDt-C8XMtqim>(8@q7 S1Fa0SGSJFED+B*01OEYQKn^Vc literal 0 HcmV?d00001 diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 66a3ca8d95a..3ba9649c92c 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -1872,12 +1872,6 @@ class SEQUENCER_PT_time(SequencerButtonsPanel, Panel): split.label(text="Channel") split.prop(strip, "channel", text="") - if not is_effect: - split = layout.split(factor=0.5 + max_factor) - split.alignment = 'RIGHT' - split.label(text="Speed Factor") - split.prop(strip, "speed_factor", text="") - sub = layout.column(align=True) split = sub.split(factor=0.5 + max_factor, align=True) split.alignment = 'RIGHT' diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 533d0976af0..20f96ee9702 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2620,6 +2620,18 @@ class _defs_sequencer_generic: options={"KEYMAP_FALLBACK"}, ) + @ToolDef.from_fn + def retime(): + return dict( + idname="builtin.retime", + label="Retime", + icon="ops.sequencer.retime", + widget="SEQUENCER_GGT_gizmo_retime", + operator=None, + keymap=None, + options={'KEYMAP_FALLBACK'}, + ) + @ToolDef.from_fn def sample(): return dict( @@ -3308,6 +3320,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel): "SEQUENCER": [ *_tools_select, _defs_sequencer_generic.blade, + _defs_sequencer_generic.retime, ], "SEQUENCER_PREVIEW": [ *_tools_select, diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 4cadc4ff028..0241a882396 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -77,6 +77,7 @@ #include "SEQ_channels.h" #include "SEQ_iterator.h" +#include "SEQ_retiming.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" @@ -685,6 +686,25 @@ static bool seq_speed_factor_set(Sequence *seq, void *user_data) return true; } +static bool do_versions_sequencer_init_retiming_tool_data(Sequence *seq, void *user_data) +{ + const Scene *scene = static_cast(user_data); + + if (seq->speed_factor == 1 || !SEQ_retiming_is_allowed(seq)) { + return true; + } + + const int content_length = SEQ_time_strip_length_get(scene, seq); + + SEQ_retiming_data_ensure(scene, seq); + + SeqRetimingHandle *handle = &seq->retiming_handles[seq->retiming_handle_num - 1]; + handle->strip_frame_index = round_fl_to_int(content_length / seq->speed_factor); + seq->speed_factor = 0.0f; + + return true; +} + static void version_geometry_nodes_replace_transfer_attribute_node(bNodeTree *ntree) { using namespace blender; @@ -1205,6 +1225,16 @@ void do_versions_after_linking_300(Main *bmain, ReportList * /*reports*/) */ { /* Keep this block, even when empty. */ + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + Editing *ed = SEQ_editing_get(scene); + if (ed == nullptr) { + continue; + } + + SEQ_for_each_callback( + &scene->ed->seqbase, do_versions_sequencer_init_retiming_tool_data, scene); + } } } diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt index 0a8130ffb56..d2b91d2bcb1 100644 --- a/source/blender/editors/datafiles/CMakeLists.txt +++ b/source/blender/editors/datafiles/CMakeLists.txt @@ -867,6 +867,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES ops.sculpt.mask_by_color ops.sculpt.mesh_filter ops.sequencer.blade + ops.sequencer.retime ops.transform.bone_envelope ops.transform.bone_size ops.transform.edge_slide diff --git a/source/blender/editors/space_sequencer/CMakeLists.txt b/source/blender/editors/space_sequencer/CMakeLists.txt index 842ba8d7fe8..1a276ce2090 100644 --- a/source/blender/editors/space_sequencer/CMakeLists.txt +++ b/source/blender/editors/space_sequencer/CMakeLists.txt @@ -35,10 +35,13 @@ set(SRC sequencer_drag_drop.c sequencer_draw.c sequencer_edit.c + sequencer_gizmo_retime.cc + sequencer_gizmo_retime_type.cc sequencer_modifier.c sequencer_ops.c sequencer_preview.c sequencer_proxy.c + sequencer_retiming.cc sequencer_scopes.c sequencer_select.c sequencer_thumbnails.c diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc new file mode 100644 index 00000000000..9dcedbfdc4d --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" + +#include "BLI_span.hh" + +#include "RNA_access.h" + +#include "UI_resources.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gizmo_library.h" +#include "ED_gizmo_utils.h" + +#include "SEQ_iterator.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" + +/* Own include. */ +#include "sequencer_intern.h" + +typedef struct GizmoGroup_retime { + wmGizmo *add_handle_gizmo; + wmGizmo *move_handle_gizmo; + wmGizmo *remove_handle_gizmo; +} GizmoGroup_retime; + +static bool gizmogroup_retime_poll(const bContext *C, wmGizmoGroupType *gzgt) +{ + /* Needed to prevent drawing gizmos when retiming tool is not activated. */ + if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) { + return false; + } + + if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) { + return false; + } + + ScrArea *area = CTX_wm_area(C); + if (area == nullptr && area->spacetype != SPACE_SEQ) { + return false; + } + + const SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first; + if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_TOOL)) { + return false; + } + + Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + Sequence *seq = ed->act_seq; + + if (ed == nullptr || seq == nullptr || !SEQ_retiming_is_allowed(seq)) { + return false; + } + + return true; +} + +static void gizmogroup_retime_setup(const bContext * /* C */, wmGizmoGroup *gzgroup) +{ + GizmoGroup_retime *ggd = (GizmoGroup_retime *)MEM_callocN(sizeof(GizmoGroup_retime), __func__); + + /* Assign gizmos. */ + const wmGizmoType *gzt_add_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_add", true); + ggd->add_handle_gizmo = WM_gizmo_new_ptr(gzt_add_handle, gzgroup, nullptr); + const wmGizmoType *gzt_remove_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_remove", true); + ggd->remove_handle_gizmo = WM_gizmo_new_ptr(gzt_remove_handle, gzgroup, nullptr); + const wmGizmoType *gzt_move_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_move", true); + ggd->move_handle_gizmo = WM_gizmo_new_ptr(gzt_move_handle, gzgroup, nullptr); + gzgroup->customdata = ggd; + + /* Assign operators. */ + wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_move", true); + WM_gizmo_operator_set(ggd->move_handle_gizmo, 0, ot, nullptr); + ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_add", true); + WM_gizmo_operator_set(ggd->add_handle_gizmo, 0, ot, nullptr); + ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_remove", true); + WM_gizmo_operator_set(ggd->remove_handle_gizmo, 0, ot, nullptr); +} + +void SEQUENCER_GGT_gizmo_retime(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Transform Gizmo Retime"; + gzgt->idname = "SEQUENCER_GGT_gizmo_retime"; + + gzgt->flag = WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL; + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; + + gzgt->poll = gizmogroup_retime_poll; + gzgt->setup = gizmogroup_retime_setup; +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc new file mode 100644 index 00000000000..ac224c08d7f --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc @@ -0,0 +1,568 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_span.hh" + +#include "DNA_anim_types.h" +#include "DNA_sequence_types.h" + +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_scene.h" + +#include "BLF_api.h" + +#include "GPU_batch.h" +#include "GPU_batch_utils.h" +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_select.h" +#include "GPU_state.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gizmo_library.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "SEQ_iterator.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" + +/* Own include. */ +#include "sequencer_intern.h" + +using blender::MutableSpan; + +#define REMOVE_GIZMO_HEIGHT 14.0f * U.dpi_fac /* Pixels from bottom of strip. */ +#define RETIME_HANDLE_TRIANGLE_SIZE 14.0f * U.dpi_fac /* Size in pixels. */ +#define RETIME_HANDLE_MOUSEOVER_THRESHOLD 16.0f * U.dpi_fac /* Size in pixels. */ +#define RETIME_BUTTON_SIZE 0.6f /* Factor based on icon size. */ + +static float strip_y_rescale(const Sequence *seq, const float y_value) +{ + const float y_range = SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM; + return (y_value * y_range) + seq->machine + SEQ_STRIP_OFSBOTTOM; +} + +static float handle_x_get(const Sequence *seq, const SeqRetimingHandle *handle) +{ + + const SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + const bool is_last_handle = (handle == last_handle); + + return SEQ_time_start_frame_get(seq) + handle->strip_frame_index + (is_last_handle ? 1 : 0); +} + +static const SeqRetimingHandle *mouse_over_handle_get(const Sequence *seq, + const View2D *v2d, + const int mval[2]) +{ + int best_distance = INT_MAX; + const SeqRetimingHandle *best_handle = nullptr; + + MutableSpan handles = SEQ_retiming_handles_get(seq); + for (const SeqRetimingHandle &handle : handles) { + int distance = round_fl_to_int( + fabsf(UI_view2d_view_to_region_x(v2d, handle_x_get(seq, &handle)) - mval[0])); + + if (distance < RETIME_HANDLE_MOUSEOVER_THRESHOLD && distance < best_distance) { + best_distance = distance; + best_handle = &handle; + } + } + + return best_handle; +} + +static float pixels_to_view_width(const bContext *C, const float width) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + float scale_x = UI_view2d_view_to_region_x(v2d, 1) - UI_view2d_view_to_region_x(v2d, 0.0f); + return width / scale_x; +} + +static float pixels_to_view_height(const bContext *C, const float height) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + float scale_y = UI_view2d_view_to_region_y(v2d, 1) - UI_view2d_view_to_region_y(v2d, 0.0f); + return height / scale_y; +} + +static float strip_start_screenspace_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + const Scene *scene = CTX_data_scene(C); + return UI_view2d_view_to_region_x(v2d, SEQ_time_left_handle_frame_get(scene, seq)); +} + +static float strip_end_screenspace_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + const Scene *scene = CTX_data_scene(C); + return UI_view2d_view_to_region_x(v2d, SEQ_time_right_handle_frame_get(scene, seq)); +} + +static Sequence *active_seq_from_context(const bContext *C) +{ + const Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + return ed->act_seq; +} + +static rctf strip_box_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + rctf rect; + rect.xmin = strip_start_screenspace_get(C, seq); + rect.xmax = strip_end_screenspace_get(C, seq); + rect.ymin = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0)); + rect.ymax = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1)); + return rect; +} + +static rctf remove_box_get(const bContext *C, const Sequence *seq) +{ + rctf rect = strip_box_get(C, seq); + rect.ymax = rect.ymin + REMOVE_GIZMO_HEIGHT; + return rect; +} + +static bool mouse_is_inside_box(const rctf *box, const int mval[2]) +{ + return mval[0] >= box->xmin && mval[0] <= box->xmax && mval[1] >= box->ymin && + mval[1] <= box->ymax; +} + +/* -------------------------------------------------------------------- */ +/** \name Retiming Add Handle Gizmo + * \{ */ + +typedef struct RetimeButtonGizmo { + wmGizmo gizmo; + int icon_id; + const Sequence *seq_under_mouse; + bool is_mouse_over_gizmo; +} RetimeButtonGizmo; + +typedef struct ButtonDimensions { + float height; + float width; + float x; + float y; +} ButtonDimensions; + +static ButtonDimensions button_dimensions_get(const bContext *C, const RetimeButtonGizmo *gizmo) +{ + const Scene *scene = CTX_data_scene(C); + const View2D *v2d = UI_view2d_fromcontext(C); + const Sequence *seq = active_seq_from_context(C); + + const float icon_height = UI_icon_get_height(gizmo->icon_id) * U.dpi_fac; + const float icon_width = UI_icon_get_width(gizmo->icon_id) * U.dpi_fac; + const float icon_x = UI_view2d_view_to_region_x(v2d, BKE_scene_frame_get(scene)) + + icon_width / 2; + const float icon_y = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.5)) - + icon_height / 2; + const ButtonDimensions dimensions = {icon_height, icon_width, icon_x, icon_y}; + return dimensions; +} + +static rctf button_box_get(const bContext *C, const RetimeButtonGizmo *gizmo) +{ + ButtonDimensions button_dimensions = button_dimensions_get(C, gizmo); + + float x_range = button_dimensions.width; + float y_range = button_dimensions.height; + + rctf rect; + rect.xmin = button_dimensions.x; + rect.xmax = button_dimensions.x + x_range; + rect.ymin = button_dimensions.y; + rect.ymax = button_dimensions.y + y_range; + + return rect; +} + +static void gizmo_retime_handle_add_draw(const bContext *C, wmGizmo *gz) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + + if (ED_screen_animation_playing(CTX_wm_manager(C))) { + return; + } + + const ButtonDimensions button = button_dimensions_get(C, gizmo); + const rctf strip_box = strip_box_get(C, active_seq_from_context(C)); + if (!BLI_rctf_isect_pt(&strip_box, button.x, button.y)) { + return; + } + + wmOrtho2_region_pixelspace(CTX_wm_region(C)); + GPU_blend(GPU_BLEND_ALPHA); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + const float alpha = gizmo->is_mouse_over_gizmo ? 1.0f : 0.6f; + + immUniformColor4f(0.2f, 0.2f, 0.2f, alpha); + imm_draw_circle_fill_2d(pos, + button.x + button.width / 2, + button.y + button.height / 2, + button.width * RETIME_BUTTON_SIZE, + 32); + immUnbindProgram(); + + GPU_polygon_smooth(false); + UI_icon_draw_alpha(button.x, button.y, gizmo->icon_id, alpha); + GPU_polygon_smooth(true); + + GPU_blend(GPU_BLEND_NONE); +} + +static int gizmo_retime_handle_add_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + Sequence *seq = active_seq_from_context(C); + + Sequence *mouse_over_seq = nullptr; + gizmo->is_mouse_over_gizmo = false; + + /* Store strip under mouse cursor. */ + const rctf strip_box = strip_box_get(C, seq); + if (mouse_is_inside_box(&strip_box, mval)) { + mouse_over_seq = seq; + } + + if (gizmo->seq_under_mouse != mouse_over_seq) { + gizmo->seq_under_mouse = mouse_over_seq; + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C)); + } + + if (gizmo->seq_under_mouse == nullptr) { + return -1; + } + + const rctf button_box = button_box_get(C, gizmo); + if (!mouse_is_inside_box(&button_box, mval)) { + return -1; + } + + gizmo->is_mouse_over_gizmo = true; + const Scene *scene = CTX_data_scene(C); + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "timeline_frame", BKE_scene_frame_get(scene)); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C)); + return 0; +} + +static void gizmo_retime_handle_add_setup(wmGizmo *gz) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + gizmo->icon_id = ICON_ADD; +} + +void GIZMO_GT_retime_handle_add(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_add"; + + /* Api callbacks. */ + gzt->setup = gizmo_retime_handle_add_setup; + gzt->draw = gizmo_retime_handle_add_draw; + gzt->test_select = gizmo_retime_handle_add_test_select; + gzt->struct_size = sizeof(RetimeButtonGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Move Handle Gizmo + * \{ */ + +typedef struct RetimeHandleMoveGizmo { + wmGizmo gizmo; + const Sequence *mouse_over_seq; + int mouse_over_handle_x; +} RetimeHandleMoveGizmo; + +static void retime_handle_draw(const bContext *C, + const RetimeHandleMoveGizmo *gizmo, + uint pos, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + const Scene *scene = CTX_data_scene(C); + const float handle_x = handle_x_get(seq, handle); + + if (handle_x == SEQ_time_left_handle_frame_get(scene, seq)) { + return; + } + if (handle_x == SEQ_time_right_handle_frame_get(scene, seq)) { + return; + } + + const View2D *v2d = UI_view2d_fromcontext(C); + const rctf strip_box = strip_box_get(C, seq); + if (!BLI_rctf_isect_x(&strip_box, UI_view2d_view_to_region_x(v2d, handle_x))) { + return; /* Handle out of strip bounds. */ + } + + const int ui_triangle_size = RETIME_HANDLE_TRIANGLE_SIZE; + const float bottom = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.0f)) + 2; + const float top = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1.0f)) - 2; + const float handle_position = UI_view2d_view_to_region_x(v2d, handle_x); + + if (seq == gizmo->mouse_over_seq && handle_x == gizmo->mouse_over_handle_x) { + immUniformColor4f(1.0f, 1.0f, 1.0f, 1.0f); + } + else { + immUniformColor4f(0.65f, 0.65f, 0.65f, 1.0f); + } + + immBegin(GPU_PRIM_TRI_FAN, 3); + immVertex2f(pos, handle_position - ui_triangle_size / 2, bottom); + immVertex2f(pos, handle_position + ui_triangle_size / 2, bottom); + immVertex2f(pos, handle_position, bottom + ui_triangle_size); + + immEnd(); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, handle_position, bottom); + immVertex2f(pos, handle_position, top); + immEnd(); +} + +static void retime_speed_text_draw(const bContext *C, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle == last_handle) { + return; + } + + const Scene *scene = CTX_data_scene(C); + const int start_frame = SEQ_time_left_handle_frame_get(scene, seq); + const int end_frame = SEQ_time_right_handle_frame_get(scene, seq); + + int next_handle_index = SEQ_retiming_handle_index_get(seq, handle) + 1; + const SeqRetimingHandle *next_handle = &SEQ_retiming_handles_get(seq)[next_handle_index]; + if (handle_x_get(seq, next_handle) < start_frame || handle_x_get(seq, handle) > end_frame) { + return; /* Label out of strip bounds. */ + } + + const float speed = SEQ_retiming_handle_speed_get(scene, seq, next_handle); + + char label_str[20]; + const size_t label_len = BLI_snprintf_rlen( + label_str, sizeof(label_str), "%d%%", round_fl_to_int(speed * 100.0f)); + + const float width = pixels_to_view_width(C, BLF_width(BLF_default(), label_str, label_len)); + + const float xmin = max_ff(SEQ_time_left_handle_frame_get(scene, seq), handle_x_get(seq, handle)); + const float xmax = min_ff(SEQ_time_right_handle_frame_get(scene, seq), + handle_x_get(seq, next_handle)); + + const float text_x = (xmin + xmax - width) / 2; + const float text_y = strip_y_rescale(seq, 0) + pixels_to_view_height(C, 5); + + if (width > xmax - xmin) { + return; /* Not enough space to draw label. */ + } + + const uchar col[4] = {255, 255, 255, 255}; + UI_view2d_text_cache_add(UI_view2d_fromcontext(C), text_x, text_y, label_str, label_len, col); +} + +static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz) +{ + const RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz; + const View2D *v2d = UI_view2d_fromcontext(C); + + wmOrtho2_region_pixelspace(CTX_wm_region(C)); + GPU_blend(GPU_BLEND_ALPHA); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + MutableSpan handles = SEQ_retiming_handles_get(seq); + + for (const SeqRetimingHandle &handle : handles) { + retime_speed_text_draw(C, seq, &handle); + + if (&handle == handles.begin()) { + continue; /* Ignore first handle. */ + } + retime_handle_draw(C, gizmo, pos, seq, &handle); + } + + immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); + + UI_view2d_text_cache_draw(CTX_wm_region(C)); + UI_view2d_view_ortho(v2d); /* 'UI_view2d_text_cache_draw()' messes up current view. */ +} + +static int gizmo_retime_handle_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz; + Scene *scene = CTX_data_scene(C); + + gizmo->mouse_over_seq = nullptr; + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval); + const int handle_index = SEQ_retiming_handle_index_get(seq, handle); + + if (handle == nullptr) { + return -1; + } + + if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) || + handle_index == 0) { + return -1; + } + + rctf strip_box = strip_box_get(C, seq); + BLI_rctf_resize_x(&strip_box, BLI_rctf_size_x(&strip_box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE); + if (!mouse_is_inside_box(&strip_box, mval)) { + return -1; + } + + gizmo->mouse_over_seq = seq; + gizmo->mouse_over_handle_x = handle_x_get(seq, handle); + + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "handle_index", handle_index); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return 0; +} + +static int gizmo_retime_handle_cursor_get(wmGizmo *gz) +{ + if (RNA_boolean_get(gz->ptr, "show_drag")) { + return WM_CURSOR_EW_SCROLL; + } + return WM_CURSOR_DEFAULT; +} + +static void gizmo_retime_handle_setup(wmGizmo *gz) +{ + gz->flag = WM_GIZMO_DRAW_MODAL; +} + +void GIZMO_GT_retime_handle(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_move"; + + /* Api callbacks. */ + gzt->setup = gizmo_retime_handle_setup; + gzt->draw = gizmo_retime_handle_draw; + gzt->test_select = gizmo_retime_handle_test_select; + gzt->cursor_get = gizmo_retime_handle_cursor_get; + gzt->struct_size = sizeof(RetimeHandleMoveGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Remove Handle Gizmo + * \{ */ + +static void gizmo_retime_remove_draw(const bContext * /* C */, wmGizmo * /* gz */) +{ + /* Pass. */ +} + +static int gizmo_retime_remove_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + Scene *scene = CTX_data_scene(C); + Sequence *seq = active_seq_from_context(C); + + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval); + const int handle_index = SEQ_retiming_handle_index_get(seq, handle); + + if (handle == nullptr) { + return -1; + } + + if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) || + handle_index == 0) { + return -1; /* Ignore first handle. */ + } + + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle == last_handle) { + return -1; /* Last handle can not be removed. */ + } + + rctf box = remove_box_get(C, seq); + + BLI_rctf_resize_x(&box, BLI_rctf_size_x(&box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE); + if (!mouse_is_inside_box(&box, mval)) { + return -1; + } + + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "handle_index", handle_index); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return 0; +} + +static int gizmo_retime_remove_cursor_get(wmGizmo *gz) +{ + if (RNA_boolean_get(gz->ptr, "show_drag")) { + return WM_CURSOR_ERASER; + } + return WM_CURSOR_DEFAULT; +} + +void GIZMO_GT_retime_remove(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_remove"; + + /* Api callbacks. */ + gzt->draw = gizmo_retime_remove_draw; + gzt->test_select = gizmo_retime_remove_test_select; + gzt->cursor_get = gizmo_retime_remove_cursor_get; + gzt->struct_size = sizeof(wmGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index c6bc2bf7dec..d88ff6a5dff 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -10,11 +10,17 @@ #include "DNA_sequence_types.h" #include "RNA_access.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Internal exports only. */ struct ARegion; struct ARegionType; struct Depsgraph; +struct wmGizmoGroupType; +struct wmGizmoType; struct Main; struct Scene; struct SeqCollection; @@ -298,3 +304,21 @@ int sequencer_image_seq_get_minmax_frame(struct wmOperator *op, int *r_numdigits); void sequencer_image_seq_reserve_frames( struct wmOperator *op, struct StripElem *se, int len, int minframe, int numdigits); + +/* sequencer_retiming.c */ +void SEQUENCER_OT_retiming_reset(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_move(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_add(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_remove(struct wmOperatorType *ot); + +/* sequencer_gizmo_retime.c */ +void SEQUENCER_GGT_gizmo_retime(struct wmGizmoGroupType *gzgt); + +/* sequencer_gizmo_retime_type.c */ +void GIZMO_GT_retime_handle_add(struct wmGizmoType *gzt); +void GIZMO_GT_retime_handle(struct wmGizmoType *gzt); +void GIZMO_GT_retime_remove(struct wmGizmoType *gzt); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c index e0e2cfa42cc..bd2d53c1fca 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.c +++ b/source/blender/editors/space_sequencer/sequencer_ops.c @@ -68,6 +68,12 @@ void sequencer_operatortypes(void) WM_operatortype_append(SEQUENCER_OT_cursor_set); WM_operatortype_append(SEQUENCER_OT_scene_frame_range_update); + /* sequencer_retiming.c */ + WM_operatortype_append(SEQUENCER_OT_retiming_reset); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_move); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_add); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_remove); + /* sequencer_select.c */ WM_operatortype_append(SEQUENCER_OT_select_all); WM_operatortype_append(SEQUENCER_OT_select); diff --git a/source/blender/editors/space_sequencer/sequencer_retiming.cc b/source/blender/editors/space_sequencer/sequencer_retiming.cc new file mode 100644 index 00000000000..89a2f5d2778 --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_retiming.cc @@ -0,0 +1,377 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "SEQ_iterator.h" +#include "SEQ_relations.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" + +#include "WM_api.h" + +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "DEG_depsgraph.h" + +/* Own include. */ +#include "sequencer_intern.h" + +using blender::MutableSpan; + +static bool retiming_poll(bContext *C) +{ + if (!sequencer_edit_poll(C)) { + return false; + } + + const Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + Sequence *seq = ed->act_seq; + + if (!SEQ_retiming_is_allowed(seq)) { + CTX_wm_operator_poll_msg_set(C, "This strip type can not be retimed"); + return false; + } + return true; +} + +static void retiming_handle_overlap(Scene *scene, Sequence *seq) +{ + ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); + SeqCollection *strips = SEQ_collection_create(__func__); + SEQ_collection_append_strip(seq, strips); + SeqCollection *dependant = SEQ_collection_create(__func__); + SEQ_collection_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain); + SEQ_collection_remove_strip(seq, dependant); + SEQ_transform_handle_overlap(scene, seqbase, strips, dependant, true); + SEQ_collection_free(strips); + SEQ_collection_free(dependant); +} + +/*-------------------------------------------------------------------- */ +/** \name Retiming Reset + * \{ */ + +static int sequencer_retiming_reset_exec(bContext *C, wmOperator * /* op */) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + SEQ_retiming_data_clear(seq); + + retiming_handle_overlap(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +void SEQUENCER_OT_retiming_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset Retiming"; + ot->description = "Reset strip retiming"; + ot->idname = "SEQUENCER_OT_retiming_reset"; + + /* api callbacks */ + ot->exec = sequencer_retiming_reset_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Move Handle + * \{ */ + +static SeqRetimingHandle *closest_retiming_handle_get(const bContext *C, + const Sequence *seq, + const float mouse_x) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + int best_distance = INT_MAX; + SeqRetimingHandle *closest_handle = nullptr; + + const float distance_threshold = UI_view2d_region_to_view_x(v2d, 10); + const float mouse_x_view = UI_view2d_region_to_view_x(v2d, mouse_x); + + for (int i = 0; i < SEQ_retiming_handles_count(seq); i++) { + SeqRetimingHandle *handle = seq->retiming_handles + i; + const int distance = round_fl_to_int(fabsf(handle->strip_frame_index - mouse_x_view)); + + if (distance < distance_threshold && distance < best_distance) { + best_distance = distance; + closest_handle = handle; + } + } + return closest_handle; +} + +static int sequencer_retiming_handle_move_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + int handle_index = 0; + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + handle_index = RNA_int_get(op->ptr, "handle_index"); + } + + /* Ensure retiming handle at left handle position. This way user gets more predictable result + * when strips have offsets. */ + const int left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq); + if (SEQ_retiming_add_handle(seq, left_handle_frame) != nullptr) { + handle_index++; /* Advance index, because new handle was created. */ + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = nullptr; + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + BLI_assert(handle_index < handles.size()); + handle = &handles[handle_index]; + } + else { + handle = closest_retiming_handle_get(C, seq, event->mval[0]); + } + + if (handle == nullptr) { + BKE_report(op->reports, RPT_ERROR, "No handle available"); + return OPERATOR_CANCELLED; + } + + op->customdata = handle; + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int sequencer_retiming_handle_move_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + const ARegion *region = CTX_wm_region(C); + const View2D *v2d = ®ion->v2d; + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + switch (event->type) { + case MOUSEMOVE: { + float mouse_x = UI_view2d_region_to_view_x(v2d, event->mval[0]); + int offset = 0; + + SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata; + SeqRetimingHandle *handle_prev = handle - 1; + + /* Limit retiming handle movement. */ + int xmin = SEQ_time_start_frame_get(seq) + handle_prev->strip_frame_index + 1; + mouse_x = max_ff(xmin, mouse_x); + offset = mouse_x - (SEQ_time_start_frame_get(seq) + handle->strip_frame_index); + + SEQ_retiming_offset_handle(scene, seq, handle, offset); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_RUNNING_MODAL; + } + case LEFTMOUSE: + case EVT_RETKEY: + case EVT_SPACEKEY: { + retiming_handle_overlap(scene, seq); + DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; + } + + case EVT_ESCKEY: + case RIGHTMOUSE: { + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_CANCELLED; + } + } + return OPERATOR_RUNNING_MODAL; +} + +void SEQUENCER_OT_retiming_handle_move(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Move Retiming Handle"; + ot->description = "Move retiming handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_move"; + + /* api callbacks */ + ot->invoke = sequencer_retiming_handle_move_invoke; + ot->modal = sequencer_retiming_handle_move_modal; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "handle_index", + 0, + 0, + INT_MAX, + "Handle Index", + "Index of handle to be moved", + 0, + INT_MAX); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Add Handle + * \{ */ + +static int sequesequencer_retiming_handle_add_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + float timeline_frame; + if (RNA_struct_property_is_set(op->ptr, "timeline_frame")) { + timeline_frame = RNA_int_get(op->ptr, "timeline_frame"); + } + else { + timeline_frame = BKE_scene_frame_get(scene); + } + + bool inserted = false; + const float end_frame = seq->start + SEQ_time_strip_length_get(scene, seq); + if (seq->start < timeline_frame && end_frame > timeline_frame) { + SEQ_retiming_add_handle(seq, timeline_frame); + inserted = true; + } + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + + return inserted ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH; +} + +void SEQUENCER_OT_retiming_handle_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Retiming Handle"; + ot->description = "Add retiming Handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_add"; + + /* api callbacks */ + ot->exec = sequesequencer_retiming_handle_add_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "timeline_frame", + 0, + 0, + INT_MAX, + "Timeline Frame", + "Frame where handle will be added", + 0, + INT_MAX); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Remove Handle + * \{ */ + +static int sequencer_retiming_handle_remove_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata; + SEQ_retiming_remove_handle(seq, handle); + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +static int sequencer_retiming_handle_remove_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + const Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + const Sequence *seq = ed->act_seq; + + if (seq == nullptr) { + return OPERATOR_CANCELLED; + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = nullptr; + + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + const int handle_index = RNA_int_get(op->ptr, "handle_index"); + BLI_assert(handle_index < handles.size()); + handle = &handles[handle_index]; + } + else { + handle = closest_retiming_handle_get(C, seq, event->mval[0]); + } + + if (handle == nullptr) { + BKE_report(op->reports, RPT_ERROR, "No handle available"); + return OPERATOR_CANCELLED; + } + + op->customdata = handle; + return sequencer_retiming_handle_remove_exec(C, op); +} + +void SEQUENCER_OT_retiming_handle_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Retiming Handle"; + ot->description = "Remove retiming handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_remove"; + + /* api callbacks */ + ot->invoke = sequencer_retiming_handle_remove_invoke; + ot->exec = sequencer_retiming_handle_remove_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, + "handle_index", + 0, + 0, + INT_MAX, + "Handle Index", + "Index of handle to be removed", + 0, + INT_MAX); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 2f8ee37be2a..5661208dc15 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -494,10 +494,15 @@ static void sequencer_gizmos(void) wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure( &(const struct wmGizmoMapType_Params){SPACE_SEQ, RGN_TYPE_PREVIEW}); + WM_gizmotype_append(GIZMO_GT_retime_handle_add); + WM_gizmotype_append(GIZMO_GT_retime_handle); + WM_gizmotype_append(GIZMO_GT_retime_remove); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_resize); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_rotate); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo_retime); WM_gizmogrouptype_append_and_link(gzmap_type, SEQUENCER_GGT_navigate); } @@ -645,6 +650,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case ND_SEQUENCER: case ND_RENDER_RESULT: ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); break; } break; @@ -658,6 +664,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case NC_SPACE: if (wmn->data == ND_SPACE_SEQUENCER) { ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); } break; case NC_ID: @@ -668,6 +675,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case NC_SCREEN: if (ELEM(wmn->data, ND_ANIMPLAY)) { ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); } break; } @@ -1069,7 +1077,6 @@ void ED_spacetype_sequencer(void) art->on_view2d_changed = sequencer_main_region_view2d_changed; art->listener = sequencer_main_region_listener; art->message_subscribe = sequencer_main_region_message_subscribe; - /* NOTE: inclusion of #ED_KEYMAP_GIZMO is currently for scripts and isn't used by default. */ art->keymapflag = ED_KEYMAP_TOOL | ED_KEYMAP_GIZMO | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES | ED_KEYMAP_ANIMATION; BLI_addhead(&st->regiontypes, art); diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 47039c740c1..e64f3f1ab24 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -120,6 +120,12 @@ typedef struct Strip { ColorManagedColorspaceSettings colorspace_settings; } Strip; +typedef struct SeqRetimingHandle { + int strip_frame_index; + int _pad0[2]; + float retiming_factor; /* Value between 0-1 mapped to original content range. */ +} SeqRetimingHandle; + typedef struct SequenceRuntime { SessionUUID session_uuid; } SequenceRuntime; @@ -164,12 +170,12 @@ typedef struct Sequence { float startstill, endstill; /** Machine: the strip channel */ int machine; - int _pad3; + int _pad; /** Starting and ending points of the effect strip. Undefined for other strip types. */ int startdisp, enddisp; float sat; float mul; - float _pad; + float _pad1; short anim_preseek; /* UNUSED. */ /** Streamindex for movie or sound files with several streams. */ @@ -231,7 +237,7 @@ typedef struct Sequence { int8_t color_tag; char alpha_mode; - char _pad4[2]; + char _pad2[2]; int cache_flag; @@ -241,7 +247,7 @@ typedef struct Sequence { /* Multiview */ char views_format; - char _pad1[3]; + char _pad3[3]; struct Stereo3dFormat *stereo3d_format; struct IDProperty *prop; @@ -251,9 +257,13 @@ typedef struct Sequence { /* Playback rate of strip content in frames per second. */ float media_playback_rate; - /* Multiply strip playback speed. */ float speed_factor; + struct SeqRetimingHandle *retiming_handles; + void *_pad5; + int retiming_handle_num; + char _pad6[4]; + SequenceRuntime runtime; } Sequence; diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index a1f9bf776ae..fe143903f22 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -468,6 +468,7 @@ void RNA_api_region_view3d(struct StructRNA *srna); void RNA_api_texture(struct StructRNA *srna); void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, bool metastrip); void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop); +void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop); void RNA_api_sound(struct StructRNA *srna); void RNA_api_vfont(struct StructRNA *srna); void RNA_api_workspace(struct StructRNA *srna); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 21843a5c223..7414da00db8 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -43,6 +43,7 @@ #include "SEQ_prefetch.h" #include "SEQ_proxy.h" #include "SEQ_relations.h" +#include "SEQ_retiming.h" #include "SEQ_select.h" #include "SEQ_sequencer.h" #include "SEQ_sound.h" @@ -272,7 +273,7 @@ static int rna_SequenceEditor_elements_length(PointerRNA *ptr) return (int)olen; } -static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +static void rna_Sequence_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { Sequence *seq = (Sequence *)ptr->data; rna_iterator_array_begin(iter, @@ -283,6 +284,85 @@ static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter, NULL); } +static int rna_Sequence_retiming_handles_length(PointerRNA *ptr) +{ + return SEQ_retiming_handles_count((Sequence *)ptr->data); +} + +static void rna_SequenceEditor_retiming_handles_begin(CollectionPropertyIterator *iter, + PointerRNA *ptr) +{ + Sequence *seq = (Sequence *)ptr->data; + rna_iterator_array_begin(iter, + (void *)seq->retiming_handles, + sizeof(SeqRetimingHandle), + SEQ_retiming_handles_count(seq), + 0, + NULL); +} + +static Sequence *strip_by_handle_find(Scene *scene, SeqRetimingHandle *handle) +{ + Editing *ed = SEQ_editing_get(scene); + SeqCollection *strips = SEQ_query_all_strips_recursive(&ed->seqbase); + + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + const int retiming_handle_count = SEQ_retiming_handles_count(seq); + SeqRetimingHandle *first = seq->retiming_handles; + SeqRetimingHandle *last = seq->retiming_handles + retiming_handle_count - 1; + + if (handle >= first && handle <= last) { + return seq; + } + } + + return NULL; +} + +static void rna_Sequence_retiming_handle_remove(ID *id, SeqRetimingHandle *handle) +{ + Scene *scene = (Scene *)id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return; + } + + SEQ_retiming_remove_handle(seq, handle); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); +} + +static int rna_Sequence_retiming_handle_frame_get(PointerRNA *ptr) +{ + SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data; + Scene *scene = (Scene *)ptr->owner_id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return 0; + } + + return SEQ_time_start_frame_get(seq) + handle->strip_frame_index; +} + +static void rna_Sequence_retiming_handle_frame_set(PointerRNA *ptr, int value) +{ + SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data; + Scene *scene = (Scene *)ptr->owner_id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return; + } + + const int offset = value - SEQ_time_start_frame_get(seq) + handle->strip_frame_index; + SEQ_retiming_offset_handle(scene, seq, handle, offset); + SEQ_relations_invalidate_cache_raw(scene, seq); +} + static void rna_Sequence_views_format_update(Main *bmain, Scene *scene, PointerRNA *ptr) { rna_Sequence_invalidate_raw_update(bmain, scene, ptr); @@ -819,19 +899,6 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO); } -static void rna_Sequence_speed_factor_update(Main *bmain, Scene *scene, PointerRNA *ptr) -{ - SEQ_cache_cleanup(scene); - rna_Sequence_audio_update(bmain, scene, ptr); -} - -static void rna_Sequence_speed_factor_set(PointerRNA *ptr, float value) -{ - Sequence *seq = (Sequence *)ptr->data; - Scene *scene = (Scene *)ptr->owner_id; - SEQ_time_speed_factor_set(scene, seq, value); -} - static void rna_Sequence_pan_range( PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax) { @@ -1493,6 +1560,31 @@ static void rna_def_strip_element(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Orig FPS", "Original frames per second"); } +static void rna_def_retiming_handle(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "RetimingHandle", NULL); + RNA_def_struct_ui_text( + srna, + "Retiming Handle", + "Handle mapped to particular frame that can be moved to change playback speed"); + RNA_def_struct_sdna(srna, "SeqRetimingHandle"); + + prop = RNA_def_property(srna, "timeline_frame", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "strip_frame_index"); + RNA_def_property_int_funcs(prop, + "rna_Sequence_retiming_handle_frame_get", + "rna_Sequence_retiming_handle_frame_set", + NULL); + RNA_def_property_ui_text(prop, "Timeline Frame", "Position of retiming handle in timeline"); + + FunctionRNA *func = RNA_def_function(srna, "remove", "rna_Sequence_retiming_handle_remove"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_function_ui_description(func, "Remove retiming handle"); +} + static void rna_def_strip_crop(BlenderRNA *brna) { StructRNA *srna; @@ -2351,19 +2443,6 @@ static void rna_def_editor(BlenderRNA *brna) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } -static void rna_def_speed_factor(StructRNA *srna) -{ - PropertyRNA *prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "speed_factor"); - RNA_def_property_float_default(prop, 1.0f); - RNA_def_property_range(prop, 0.1f, FLT_MAX); - RNA_def_property_ui_range(prop, 1.0f, 100.0f, 10.0, 3); - RNA_def_property_ui_text(prop, "Speed Factor", "Multiply playback speed"); - RNA_def_property_float_funcs( - prop, NULL, "rna_Sequence_speed_factor_set", NULL); /* overlap test */ - RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_speed_factor_update"); -} - static void rna_def_filter_video(StructRNA *srna) { PropertyRNA *prop; @@ -2564,7 +2643,7 @@ static void rna_def_image(BlenderRNA *brna) RNA_def_property_struct_type(prop, "SequenceElement"); RNA_def_property_ui_text(prop, "Elements", ""); RNA_def_property_collection_funcs(prop, - "rna_SequenceEditor_elements_begin", + "rna_Sequence_elements_begin", "rna_iterator_array_next", "rna_iterator_array_end", "rna_iterator_array_get", @@ -2596,7 +2675,6 @@ static void rna_def_image(BlenderRNA *brna) rna_def_proxy(srna); rna_def_input(srna); rna_def_color_management(srna); - rna_def_speed_factor(srna); } static void rna_def_meta(BlenderRNA *brna) @@ -2628,7 +2706,6 @@ static void rna_def_meta(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_proxy(srna); rna_def_input(srna); - rna_def_speed_factor(srna); } static void rna_def_scene(BlenderRNA *brna) @@ -2677,7 +2754,6 @@ static void rna_def_scene(BlenderRNA *brna) rna_def_proxy(srna); rna_def_input(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_movie(BlenderRNA *brna) @@ -2705,7 +2781,7 @@ static void rna_def_movie(BlenderRNA *brna) RNA_def_property_struct_type(prop, "SequenceElement"); RNA_def_property_ui_text(prop, "Elements", ""); RNA_def_property_collection_funcs(prop, - "rna_SequenceEditor_elements_begin", + "rna_Sequence_elements_begin", "rna_iterator_array_next", "rna_iterator_array_end", "rna_iterator_array_get", @@ -2714,6 +2790,21 @@ static void rna_def_movie(BlenderRNA *brna) NULL, NULL); + prop = RNA_def_property(srna, "retiming_handles", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "retiming_handles", NULL); + RNA_def_property_struct_type(prop, "RetimingHandle"); + RNA_def_property_ui_text(prop, "Retiming Hndles", ""); + RNA_def_property_collection_funcs(prop, + "rna_SequenceEditor_retiming_handles_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + "rna_Sequence_retiming_handles_length", + NULL, + NULL, + NULL); + RNA_api_sequence_retiming_handles(brna, prop); + prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH); RNA_def_property_ui_text(prop, "File", ""); RNA_def_property_string_funcs(prop, @@ -2761,7 +2852,6 @@ static void rna_def_movie(BlenderRNA *brna) rna_def_input(srna); rna_def_color_management(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_movieclip(BlenderRNA *brna) @@ -2789,7 +2879,6 @@ static void rna_def_movieclip(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_input(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_mask(BlenderRNA *brna) @@ -2808,7 +2897,6 @@ static void rna_def_mask(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_input(srna); - rna_def_speed_factor(srna); } static void rna_def_sound(BlenderRNA *brna) @@ -2851,7 +2939,6 @@ static void rna_def_sound(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL); rna_def_input(srna); - rna_def_speed_factor(srna); } static void rna_def_effect(BlenderRNA *brna) @@ -3613,6 +3700,7 @@ void RNA_def_sequencer(BlenderRNA *brna) rna_def_color_balance(brna); rna_def_strip_element(brna); + rna_def_retiming_handle(brna); rna_def_strip_proxy(brna); rna_def_strip_color_balance(brna); rna_def_strip_crop(brna); diff --git a/source/blender/makesrna/intern/rna_sequencer_api.c b/source/blender/makesrna/intern/rna_sequencer_api.c index e1ccffdd2c1..c12c3ffe6b0 100644 --- a/source/blender/makesrna/intern/rna_sequencer_api.c +++ b/source/blender/makesrna/intern/rna_sequencer_api.c @@ -44,6 +44,7 @@ # include "SEQ_effects.h" # include "SEQ_relations.h" # include "SEQ_render.h" +# include "SEQ_retiming.h" # include "SEQ_sequencer.h" # include "SEQ_time.h" @@ -637,6 +638,29 @@ static void rna_Sequence_invalidate_cache_rnafunc(ID *id, Sequence *self, int ty } } +static SeqRetimingHandle *rna_Sequence_retiming_handles_add(ID *id, + Sequence *seq, + int timeline_frame) +{ + Scene *scene = (Scene *)id; + + SeqRetimingHandle *handle = SEQ_retiming_add_handle(seq, timeline_frame); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); + return handle; +} + +static void rna_Sequence_retiming_handles_reset(ID *id, Sequence *seq) +{ + Scene *scene = (Scene *)id; + + SEQ_retiming_data_clear(seq); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); +} + #else void RNA_api_sequence_strip(StructRNA *srna) @@ -744,6 +768,30 @@ void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } +void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + + RNA_def_property_srna(cprop, "RetimingHandles"); + srna = RNA_def_struct(brna, "RetimingHandles", NULL); + RNA_def_struct_sdna(srna, "Sequence"); + RNA_def_struct_ui_text(srna, "RetimingHandles", "Collection of RetimingHandle"); + + FunctionRNA *func = RNA_def_function(srna, "add", "rna_Sequence_retiming_handles_add"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_int( + func, "timeline_frame", 0, -MAXFRAME, MAXFRAME, "Timeline Frame", "", -MAXFRAME, MAXFRAME); + RNA_def_function_ui_description(func, "Add retiming handle"); + /* return type */ + PropertyRNA *parm = RNA_def_pointer( + func, "retiming_handle", "RetimingHandle", "", "New RetimingHandle"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "reset", "rna_Sequence_retiming_handles_reset"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_function_ui_description(func, "Remove all retiming handles"); +} + void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, const bool metastrip) { StructRNA *srna; diff --git a/source/blender/sequencer/CMakeLists.txt b/source/blender/sequencer/CMakeLists.txt index 76a3a060e19..7f1e38752c9 100644 --- a/source/blender/sequencer/CMakeLists.txt +++ b/source/blender/sequencer/CMakeLists.txt @@ -42,6 +42,8 @@ set(SRC SEQ_proxy.h SEQ_relations.h SEQ_render.h + SEQ_retiming.h + SEQ_retiming.hh SEQ_select.h SEQ_sequencer.h SEQ_sound.h @@ -76,6 +78,7 @@ set(SRC intern/strip_add.c intern/strip_edit.c intern/strip_relations.c + intern/strip_retiming.cc intern/strip_select.c intern/strip_time.c intern/strip_time.h diff --git a/source/blender/sequencer/SEQ_retiming.h b/source/blender/sequencer/SEQ_retiming.h new file mode 100644 index 00000000000..84254fda0ea --- /dev/null +++ b/source/blender/sequencer/SEQ_retiming.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2004 Blender Foundation. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup sequencer + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct Scene; +struct Sequence; +struct SeqRetimingHandle; + +int SEQ_retiming_handles_count(const struct Sequence *seq); +bool SEQ_retiming_is_active(const struct Sequence *seq); +void SEQ_retiming_data_ensure(const struct Scene *scene, struct Sequence *seq); +void SEQ_retiming_data_clear(struct Sequence *seq); +bool SEQ_retiming_is_allowed(const struct Sequence *seq); + +/** + * Add new retiming handle. + * This function always reallocates memory, so when function is used all stored pointers will + * become invalid. + */ +struct SeqRetimingHandle *SEQ_retiming_add_handle(struct Sequence *seq, const int timeline_frame); +struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq); +void SEQ_retiming_remove_handle(struct Sequence *seq, struct SeqRetimingHandle *handle); +void SEQ_retiming_offset_handle(const struct Scene *scene, + struct Sequence *seq, + struct SeqRetimingHandle *handle, + const int offset); +float SEQ_retiming_handle_speed_get(const struct Scene *scene, + const struct Sequence *seq, + const struct SeqRetimingHandle *handle); +int SEQ_retiming_handle_index_get(const struct Sequence *seq, + const struct SeqRetimingHandle *handle); +#ifdef __cplusplus +} +#endif diff --git a/source/blender/sequencer/SEQ_retiming.hh b/source/blender/sequencer/SEQ_retiming.hh new file mode 100644 index 00000000000..128a3020534 --- /dev/null +++ b/source/blender/sequencer/SEQ_retiming.hh @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup sequencer + */ + +#include "BLI_span.hh" + +struct Sequence; +struct SeqRetimingHandle; + +blender::MutableSpan SEQ_retiming_handles_get(const Sequence *seq); diff --git a/source/blender/sequencer/SEQ_time.h b/source/blender/sequencer/SEQ_time.h index 11dbe0dab0a..45ec3bbc8ae 100644 --- a/source/blender/sequencer/SEQ_time.h +++ b/source/blender/sequencer/SEQ_time.h @@ -136,7 +136,6 @@ void SEQ_time_start_frame_set(const struct Scene *scene, struct Sequence *seq, i * \note this function is currently only used internally and in versioning code. */ void SEQ_time_update_meta_strip_range(const struct Scene *scene, struct Sequence *seq_meta); - #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index c6f4d47ac75..58c791ad63d 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -18,6 +18,7 @@ #include "BLI_listbase.h" +#include "BKE_fcurve.h" #include "BKE_idprop.h" #include "BKE_lib_id.h" #include "BKE_sound.h" @@ -34,6 +35,7 @@ #include "SEQ_modifier.h" #include "SEQ_proxy.h" #include "SEQ_relations.h" +#include "SEQ_retiming.h" #include "SEQ_select.h" #include "SEQ_sequencer.h" #include "SEQ_sound.h" @@ -218,6 +220,12 @@ static void seq_sequence_free_ex(Scene *scene, SEQ_channels_free(&seq->channels); } + if (seq->retiming_handles != NULL) { + MEM_freeN(seq->retiming_handles); + seq->retiming_handles = NULL; + seq->retiming_handle_num = 0; + } + MEM_freeN(seq); } @@ -576,6 +584,11 @@ static Sequence *seq_dupli(const Scene *scene_src, } } + if (seq->retiming_handles != NULL) { + seqn->retiming_handles = MEM_dupallocN(seq->retiming_handles); + seqn->retiming_handle_num = seq->retiming_handle_num; + } + return seqn; } @@ -749,6 +762,12 @@ static bool seq_write_data_cb(Sequence *seq, void *userdata) LISTBASE_FOREACH (SeqTimelineChannel *, channel, &seq->channels) { BLO_write_struct(writer, SeqTimelineChannel, channel); } + + if (seq->retiming_handles != NULL) { + int size = SEQ_retiming_handles_count(seq); + BLO_write_struct_array(writer, SeqRetimingHandle, size, seq->retiming_handles); + } + return true; } @@ -818,6 +837,11 @@ static bool seq_read_data_cb(Sequence *seq, void *user_data) SEQ_modifier_blend_read_data(reader, &seq->modifiers); BLO_read_list(reader, &seq->channels); + + if (seq->retiming_handles != NULL) { + BLO_read_data_address(reader, &seq->retiming_handles); + } + return true; } void SEQ_blend_read(BlendDataReader *reader, ListBase *seqbase) diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index d0f7521bd10..b5d4f44cb39 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -275,9 +275,7 @@ static void seq_split_set_right_hold_offset(Main *bmain, /* Adjust within range of strip contents. */ else if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) { seq->endofs = 0; - float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ? - seq_time_media_playback_rate_factor_get(scene, seq) : - seq_time_playback_rate_factor_get(scene, seq); + float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq); seq->anim_endofs += round_fl_to_int((content_end - timeline_frame) * speed_factor); } @@ -296,9 +294,7 @@ static void seq_split_set_left_hold_offset(Main *bmain, /* Adjust within range of strip contents. */ if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) { - float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ? - seq_time_media_playback_rate_factor_get(scene, seq) : - seq_time_playback_rate_factor_get(scene, seq); + float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq); seq->anim_startofs += round_fl_to_int((timeline_frame - content_start) * speed_factor); seq->start = timeline_frame; seq->startofs = 0; diff --git a/source/blender/sequencer/intern/strip_retiming.cc b/source/blender/sequencer/intern/strip_retiming.cc new file mode 100644 index 00000000000..2f9c86b2b31 --- /dev/null +++ b/source/blender/sequencer/intern/strip_retiming.cc @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_sequence_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_span.hh" + +#include "BKE_fcurve.h" +#include "BKE_movieclip.h" +#include "BKE_scene.h" +#include "BKE_sound.h" + +#include "DNA_anim_types.h" +#include "DNA_sound_types.h" + +#include "IMB_imbuf.h" + +#include "RNA_prototypes.h" + +#include "SEQ_channels.h" +#include "SEQ_iterator.h" +#include "SEQ_relations.h" +#include "SEQ_render.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" + +#include "sequencer.h" +#include "strip_time.h" +#include "utils.h" + +using blender::MutableSpan; + +MutableSpan SEQ_retiming_handles_get(const Sequence *seq) +{ + blender::MutableSpan handles(seq->retiming_handles, seq->retiming_handle_num); + return handles; +} + +struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq) +{ + return seq->retiming_handles + seq->retiming_handle_num - 1; +} + +int SEQ_retiming_handle_index_get(const Sequence *seq, const SeqRetimingHandle *handle) +{ + return handle - seq->retiming_handles; +} + +static bool seq_retiming_is_last_handle(const Sequence *seq, const SeqRetimingHandle *handle) +{ + return SEQ_retiming_handle_index_get(seq, handle) == seq->retiming_handle_num - 1; +} + +static const SeqRetimingHandle *retiming_find_segment_start_handle(const Sequence *seq, + const int frame_index) +{ + const SeqRetimingHandle *start_handle = nullptr; + for (auto const &handle : SEQ_retiming_handles_get(seq)) { + if (seq_retiming_is_last_handle(seq, &handle)) { + break; + } + if (handle.strip_frame_index > frame_index) { + break; + } + + start_handle = &handle; + } + + return start_handle; +} + +int SEQ_retiming_handles_count(const Sequence *seq) +{ + return seq->retiming_handle_num; +} + +void SEQ_retiming_data_ensure(const Scene *scene, Sequence *seq) +{ + if (!SEQ_retiming_is_allowed(seq)) { + return; + } + + if (seq->retiming_handles != nullptr) { + return; + } + + seq->retiming_handles = (SeqRetimingHandle *)MEM_calloc_arrayN( + 2, sizeof(SeqRetimingHandle), __func__); + SeqRetimingHandle *handle = seq->retiming_handles + 1; + handle->strip_frame_index = seq_time_strip_original_content_length_get(scene, seq) - 1; + handle->retiming_factor = 1.0f; + seq->retiming_handle_num = 2; +} + +void SEQ_retiming_data_clear(Sequence *seq) +{ + seq->retiming_handles = nullptr; + seq->retiming_handle_num = 0; +} + +bool SEQ_retiming_is_active(const Sequence *seq) +{ + return seq->retiming_handle_num > 1; +} + +bool SEQ_retiming_is_allowed(const Sequence *seq) +{ + return ELEM(seq->type, + SEQ_TYPE_IMAGE, + SEQ_TYPE_META, + SEQ_TYPE_SCENE, + SEQ_TYPE_MOVIE, + SEQ_TYPE_MOVIECLIP, + SEQ_TYPE_MASK); +} + +float seq_retiming_evaluate(const Sequence *seq, const int frame_index) +{ + const SeqRetimingHandle *previous_handle = retiming_find_segment_start_handle(seq, frame_index); + const SeqRetimingHandle *next_handle = previous_handle + 1; + const int previous_handle_index = previous_handle - seq->retiming_handles; + + BLI_assert(previous_handle_index < seq->retiming_handle_num); + + if (next_handle == nullptr) { + return 1.0f; + } + + const int segment_length = next_handle->strip_frame_index - previous_handle->strip_frame_index; + const int segment_frame_index = frame_index - previous_handle->strip_frame_index; + const float segment_fac = segment_frame_index / (float)segment_length; + + const float target_diff = next_handle->retiming_factor - previous_handle->retiming_factor; + return previous_handle->retiming_factor + (target_diff * segment_fac); +} + +SeqRetimingHandle *SEQ_retiming_add_handle(Sequence *seq, const int timeline_frame) +{ + float frame_index = timeline_frame - SEQ_time_start_frame_get(seq); + float value = seq_retiming_evaluate(seq, frame_index); + + const SeqRetimingHandle *closest_handle = retiming_find_segment_start_handle(seq, frame_index); + if (closest_handle->strip_frame_index == frame_index) { + return nullptr; /* Retiming handle already exists. */ + } + + SeqRetimingHandle *handles = seq->retiming_handles; + size_t handle_count = SEQ_retiming_handles_count(seq); + const int new_handle_index = closest_handle - handles + 1; + BLI_assert(new_handle_index >= 0); + BLI_assert(new_handle_index < handle_count); + + SeqRetimingHandle *new_handles = (SeqRetimingHandle *)MEM_callocN( + (handle_count + 1) * sizeof(SeqRetimingHandle), __func__); + if (new_handle_index > 0) { + memcpy(new_handles, handles, new_handle_index * sizeof(SeqRetimingHandle)); + } + if (new_handle_index < handle_count) { + memcpy(new_handles + new_handle_index + 1, + handles + new_handle_index, + (handle_count - new_handle_index) * sizeof(SeqRetimingHandle)); + } + MEM_freeN(handles); + seq->retiming_handles = new_handles; + seq->retiming_handle_num++; + + SeqRetimingHandle *added_handle = (new_handles + new_handle_index); + added_handle->strip_frame_index = frame_index; + added_handle->retiming_factor = value; + + return added_handle; +} + +void SEQ_retiming_offset_handle(const Scene *scene, + Sequence *seq, + SeqRetimingHandle *handle, + const int offset) +{ + if (handle->strip_frame_index == 0) { + return; /* First handle can not be moved. */ + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + for (; handle < handles.end(); handle++) { + handle->strip_frame_index += offset; + } + + SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq)); + seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq)); +} + +void SEQ_retiming_remove_handle(Sequence *seq, SeqRetimingHandle *handle) +{ + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle->strip_frame_index == 0 || handle == last_handle) { + return; /* First and last handle can not be removed. */ + } + + size_t handle_count = SEQ_retiming_handles_count(seq); + SeqRetimingHandle *handles = (SeqRetimingHandle *)MEM_callocN( + (handle_count - 1) * sizeof(SeqRetimingHandle), __func__); + + const int handle_index = handle - seq->retiming_handles; + memcpy(handles, seq->retiming_handles, (handle_index) * sizeof(SeqRetimingHandle)); + memcpy(handles + handle_index, + seq->retiming_handles + handle_index + 1, + (handle_count - handle_index - 1) * sizeof(SeqRetimingHandle)); + MEM_freeN(seq->retiming_handles); + seq->retiming_handles = handles; + seq->retiming_handle_num--; +} + +float SEQ_retiming_handle_speed_get(const Scene *scene, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + if (handle->strip_frame_index == 0) { + return 1.0f; + } + + const SeqRetimingHandle *handle_prev = handle - 1; + + const int frame_index_max = seq_time_strip_original_content_length_get(scene, seq) - 1; + const int frame_retimed_prev = round_fl_to_int(handle_prev->retiming_factor * frame_index_max); + const int frame_index_prev = handle_prev->strip_frame_index; + const int frame_retimed = round_fl_to_int(handle->retiming_factor * frame_index_max); + const int frame_index = handle->strip_frame_index; + + const int fragment_length_retimed = frame_retimed - frame_retimed_prev; + const int fragment_length_original = frame_index - frame_index_prev; + + const float speed = (float)fragment_length_retimed / (float)fragment_length_original; + return speed; +} diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index 7ef3f0f5a47..bdefe04886f 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -7,22 +7,31 @@ * \ingroup bke */ +#include "MEM_guardedalloc.h" + #include "DNA_scene_types.h" #include "DNA_sequence_types.h" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BKE_fcurve.h" #include "BKE_movieclip.h" #include "BKE_scene.h" #include "BKE_sound.h" +#include "DNA_anim_types.h" #include "DNA_sound_types.h" + #include "IMB_imbuf.h" +#include "RNA_prototypes.h" + #include "SEQ_channels.h" #include "SEQ_iterator.h" +#include "SEQ_relations.h" #include "SEQ_render.h" +#include "SEQ_retiming.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" #include "SEQ_transform.h" @@ -44,9 +53,13 @@ float seq_time_media_playback_rate_factor_get(const Scene *scene, const Sequence return seq->media_playback_rate / scene_playback_rate; } -float seq_time_playback_rate_factor_get(const Scene *scene, const Sequence *seq) +int seq_time_strip_original_content_length_get(const Scene *scene, const Sequence *seq) { - return seq_time_media_playback_rate_factor_get(scene, seq) * seq->speed_factor; + if (seq->type == SEQ_TYPE_SOUND_RAM) { + return seq->len; + } + + return seq->len / seq_time_media_playback_rate_factor_get(scene, seq); } float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_frame) @@ -54,6 +67,7 @@ float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_fra float frame_index; float sta = SEQ_time_start_frame_get(seq); float end = SEQ_time_content_end_frame_get(scene, seq) - 1; + const float length = seq_time_strip_original_content_length_get(scene, seq); if (seq->type & SEQ_TYPE_EFFECT) { end = SEQ_time_right_handle_frame_get(scene, seq); @@ -70,9 +84,15 @@ float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_fra frame_index = timeline_frame - sta; } - /* Clamp frame index to strip frame range. */ - frame_index = clamp_f(frame_index, 0, end - sta); - frame_index *= seq_time_playback_rate_factor_get(scene, seq); + if (SEQ_retiming_is_active(seq)) { + const float retiming_factor = seq_retiming_evaluate(seq, frame_index); + frame_index = retiming_factor * (length - 1); + } + else { + frame_index *= seq_time_media_playback_rate_factor_get(scene, seq); + /* Clamp frame index to strip frame range. */ + frame_index = clamp_f(frame_index, 0, end - sta); + } if (seq->strobe < 1.0f) { seq->strobe = 1.0f; @@ -442,27 +462,6 @@ bool SEQ_time_strip_intersects_frame(const Scene *scene, (SEQ_time_right_handle_frame_get(scene, seq) > timeline_frame); } -void SEQ_time_speed_factor_set(const Scene *scene, Sequence *seq, const float speed_factor) -{ - - if (seq->type == SEQ_TYPE_SOUND_RAM) { - seq->speed_factor = speed_factor; - } - else { - const float left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq); - const float unity_start_offset = seq->startofs * seq->speed_factor; - const float unity_end_offset = seq->endofs * seq->speed_factor; - /* Left handle is pivot point for content scaling - it must always show same frame. */ - seq->speed_factor = speed_factor; - seq->startofs = unity_start_offset / speed_factor; - seq->start = left_handle_frame - seq->startofs; - seq->endofs = unity_end_offset / speed_factor; - } - - SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq)); - seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq)); -} - bool SEQ_time_has_left_still_frames(const Scene *scene, const Sequence *seq) { return SEQ_time_left_handle_frame_get(scene, seq) < SEQ_time_start_frame_get(seq); @@ -480,11 +479,13 @@ bool SEQ_time_has_still_frames(const Scene *scene, const Sequence *seq) int SEQ_time_strip_length_get(const Scene *scene, const Sequence *seq) { - if (seq->type == SEQ_TYPE_SOUND_RAM) { - return seq->len; + if (SEQ_retiming_is_active(seq)) { + SeqRetimingHandle *handle_start = seq->retiming_handles; + SeqRetimingHandle *handle_end = seq->retiming_handles + (SEQ_retiming_handles_count(seq) - 1); + return handle_end->strip_frame_index - handle_start->strip_frame_index + 1; } - return seq->len / seq_time_playback_rate_factor_get(scene, seq); + return seq_time_strip_original_content_length_get(scene, seq); } float SEQ_time_start_frame_get(const Sequence *seq) diff --git a/source/blender/sequencer/intern/strip_time.h b/source/blender/sequencer/intern/strip_time.h index c8946c47710..4fbaea079ef 100644 --- a/source/blender/sequencer/intern/strip_time.h +++ b/source/blender/sequencer/intern/strip_time.h @@ -43,7 +43,11 @@ void seq_time_update_effects_strip_range(const struct Scene *scene, struct SeqCo void seq_time_translate_handles(const struct Scene *scene, struct Sequence *seq, const int offset); float seq_time_media_playback_rate_factor_get(const struct Scene *scene, const struct Sequence *seq); -float seq_time_playback_rate_factor_get(const struct Scene *scene, const struct Sequence *seq); +int seq_time_strip_original_content_length_get(const struct Scene *scene, + const struct Sequence *seq); +float seq_retiming_evaluate(const struct Sequence *seq, const int frame_index); +float seq_time_media_playback_rate_factor_get(const struct Scene *scene, + const struct Sequence *seq); #ifdef __cplusplus } -- 2.30.2 From 03c5ee0294939ab324dfeba01d0a5fdb1c16f297 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Wed, 15 Feb 2023 00:33:25 +0100 Subject: [PATCH 2/5] Fix compilation warning --- source/blender/sequencer/intern/strip_retiming.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/sequencer/intern/strip_retiming.cc b/source/blender/sequencer/intern/strip_retiming.cc index 2f9c86b2b31..b3da8ad3f47 100644 --- a/source/blender/sequencer/intern/strip_retiming.cc +++ b/source/blender/sequencer/intern/strip_retiming.cc @@ -133,6 +133,7 @@ float seq_retiming_evaluate(const Sequence *seq, const int frame_index) const int previous_handle_index = previous_handle - seq->retiming_handles; BLI_assert(previous_handle_index < seq->retiming_handle_num); + UNUSED_VARS_NDEBUG(previous_handle_index); if (next_handle == nullptr) { return 1.0f; -- 2.30.2 From e2042ab55df8859e88830c3cfbae480137a45e30 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Wed, 15 Feb 2023 00:35:40 +0100 Subject: [PATCH 3/5] Fix crash on NULL dereference --- source/blender/editors/space_sequencer/sequencer_retiming.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_sequencer/sequencer_retiming.cc b/source/blender/editors/space_sequencer/sequencer_retiming.cc index 89a2f5d2778..84708b74e77 100644 --- a/source/blender/editors/space_sequencer/sequencer_retiming.cc +++ b/source/blender/editors/space_sequencer/sequencer_retiming.cc @@ -48,7 +48,7 @@ static bool retiming_poll(bContext *C) const Editing *ed = SEQ_editing_get(CTX_data_scene(C)); Sequence *seq = ed->act_seq; - if (!SEQ_retiming_is_allowed(seq)) { + if (seq != nullptr && !SEQ_retiming_is_allowed(seq)) { CTX_wm_operator_poll_msg_set(C, "This strip type can not be retimed"); return false; } -- 2.30.2 From 6b25ac1854377e2741b79c3a627cec2e29c4650a Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Wed, 15 Feb 2023 01:44:48 +0100 Subject: [PATCH 4/5] Fix redundant redeclaration warning --- source/blender/sequencer/intern/strip_time.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/sequencer/intern/strip_time.h b/source/blender/sequencer/intern/strip_time.h index 4fbaea079ef..fbd35dc6adf 100644 --- a/source/blender/sequencer/intern/strip_time.h +++ b/source/blender/sequencer/intern/strip_time.h @@ -46,8 +46,6 @@ float seq_time_media_playback_rate_factor_get(const struct Scene *scene, int seq_time_strip_original_content_length_get(const struct Scene *scene, const struct Sequence *seq); float seq_retiming_evaluate(const struct Sequence *seq, const int frame_index); -float seq_time_media_playback_rate_factor_get(const struct Scene *scene, - const struct Sequence *seq); #ifdef __cplusplus } -- 2.30.2 From b34ca86766c6fc2904553320f602d9d27655c055 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Tue, 21 Feb 2023 13:24:03 +0100 Subject: [PATCH 5/5] Revert removal of speed factor for sound strips --- .../scripts/startup/bl_ui/space_sequencer.py | 6 +++++ .../blender/makesrna/intern/rna_sequencer.c | 27 +++++++++++++++++++ source/blender/sequencer/intern/strip_time.c | 25 +++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 3ba9649c92c..fac00bdf1d0 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -1872,6 +1872,12 @@ class SEQUENCER_PT_time(SequencerButtonsPanel, Panel): split.label(text="Channel") split.prop(strip, "channel", text="") + if strip.type == 'SOUND': + split = layout.split(factor=0.5 + max_factor) + split.alignment = 'RIGHT' + split.label(text="Speed Factor") + split.prop(strip, "speed_factor", text="") + sub = layout.column(align=True) split = sub.split(factor=0.5 + max_factor, align=True) split.alignment = 'RIGHT' diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 7414da00db8..3c47ab3cbba 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -899,6 +899,19 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO); } +static void rna_Sequence_speed_factor_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + SEQ_cache_cleanup(scene); + rna_Sequence_audio_update(bmain, scene, ptr); +} + +static void rna_Sequence_speed_factor_set(PointerRNA *ptr, float value) +{ + Sequence *seq = (Sequence *)ptr->data; + Scene *scene = (Scene *)ptr->owner_id; + SEQ_time_speed_factor_set(scene, seq, value); +} + static void rna_Sequence_pan_range( PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax) { @@ -2443,6 +2456,19 @@ static void rna_def_editor(BlenderRNA *brna) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } +static void rna_def_speed_factor(StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "speed_factor"); + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_range(prop, 0.1f, FLT_MAX); + RNA_def_property_ui_range(prop, 1.0f, 100.0f, 10.0, 3); + RNA_def_property_ui_text(prop, "Speed Factor", "Multiply playback speed"); + RNA_def_property_float_funcs( + prop, NULL, "rna_Sequence_speed_factor_set", NULL); /* overlap test */ + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_speed_factor_update"); +} + static void rna_def_filter_video(StructRNA *srna) { PropertyRNA *prop; @@ -2939,6 +2965,7 @@ static void rna_def_sound(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL); rna_def_input(srna); + rna_def_speed_factor(srna); } static void rna_def_effect(BlenderRNA *brna) diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index bdefe04886f..84f01df8db8 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -462,6 +462,27 @@ bool SEQ_time_strip_intersects_frame(const Scene *scene, (SEQ_time_right_handle_frame_get(scene, seq) > timeline_frame); } +void SEQ_time_speed_factor_set(const Scene *scene, Sequence *seq, const float speed_factor) +{ + + if (seq->type == SEQ_TYPE_SOUND_RAM) { + seq->speed_factor = speed_factor; + } + else { + const float left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq); + const float unity_start_offset = seq->startofs * seq->speed_factor; + const float unity_end_offset = seq->endofs * seq->speed_factor; + /* Left handle is pivot point for content scaling - it must always show same frame. */ + seq->speed_factor = speed_factor; + seq->startofs = unity_start_offset / speed_factor; + seq->start = left_handle_frame - seq->startofs; + seq->endofs = unity_end_offset / speed_factor; + } + + SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq)); + seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq)); +} + bool SEQ_time_has_left_still_frames(const Scene *scene, const Sequence *seq) { return SEQ_time_left_handle_frame_get(scene, seq) < SEQ_time_start_frame_get(seq); @@ -479,6 +500,10 @@ bool SEQ_time_has_still_frames(const Scene *scene, const Sequence *seq) int SEQ_time_strip_length_get(const Scene *scene, const Sequence *seq) { + if (seq->type == SEQ_TYPE_SOUND_RAM) { + return seq->len; + } + if (SEQ_retiming_is_active(seq)) { SeqRetimingHandle *handle_start = seq->retiming_handles; SeqRetimingHandle *handle_end = seq->retiming_handles + (SEQ_retiming_handles_count(seq) - 1); -- 2.30.2