VSE: Smooth transition for retiming tool

This feature implements smooth transition between 2 retimed segments.

A transition can be added by dragging retiming handle while holding
shift key. When any handle defining transition is removed, the
transition segment is removed and 2 linear segments are restored to
state before transition was created.

These transitions work with video and sound and changes are reflected
in waveforms with correct seeking, therefore it replaces usage of
`speed_factor` or earlier sound `pitch` property.

Smooth transition is achieved by evaluating Y value of circular arc,
that is tangent to lines of linear retimed segments. Because of this,
when transition length is changed both handles are moved symetrically.
This way it is possible to make transition without affecting content in
linear segments.
When linear segment after transition is further retimed, this shifts
content inside of a transition segment.

To support sound, `SEQ_retiming_sound_animation_data_set` and related
data structures has been updated to build speed table for each smooth
transition segment. Layering of retiming data via meta strips is still
fully supported.

Pull Request: https://projects.blender.org/blender/blender/pulls/107197
This commit is contained in:
Richard Antalik 2023-05-16 22:33:13 +02:00 committed by Richard Antalik
parent 35be844c34
commit 4dc026ec8e
8 changed files with 564 additions and 81 deletions

View File

@ -154,6 +154,8 @@ void BKE_sound_set_scene_sound_volume(void *handle, float volume, char animated)
void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated);
void BKE_sound_set_scene_sound_pitch_at_frame(void *handle, int frame, float pitch, char animated);
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
int frame_start,
int frame_end,

View File

@ -819,6 +819,11 @@ void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated)
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PITCH, sound_cfra, &pitch, animated);
}
void BKE_sound_set_scene_sound_pitch_at_frame(void *handle, int frame, float pitch, char animated)
{
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PITCH, frame, &pitch, animated);
}
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
int frame_start,
int frame_end,

View File

@ -219,6 +219,15 @@ static void gizmo_retime_handle_add_draw(const bContext *C, wmGizmo *gz)
return;
}
const Scene *scene = CTX_data_scene(C);
const Sequence *seq = active_seq_from_context(C);
const int frame_index = BKE_scene_frame_get(scene) - SEQ_time_start_frame_get(seq);
const SeqRetimingHandle *handle = SEQ_retiming_find_segment_start_handle(seq, frame_index);
if (handle != nullptr && SEQ_retiming_handle_is_transition_type(handle)) {
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)) {
@ -315,6 +324,7 @@ typedef struct RetimeHandleMoveGizmo {
wmGizmo gizmo;
const Sequence *mouse_over_seq;
int mouse_over_handle_x;
bool create_transition_operation;
} RetimeHandleMoveGizmo;
static void retime_handle_draw(const bContext *C,
@ -344,13 +354,24 @@ static void retime_handle_draw(const bContext *C,
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);
float col[4] = {1.0f, 1.0f, 1.0f, 1.0f};
if (seq == gizmo->mouse_over_seq && handle_x == gizmo->mouse_over_handle_x) {
immUniformColor4f(1.0f, 1.0f, 1.0f, 1.0f);
if (gizmo->create_transition_operation) {
bool handle_is_transition = SEQ_retiming_handle_is_transition_type(handle);
bool prev_handle_is_transition = SEQ_retiming_handle_is_transition_type(handle - 1);
if (!(handle_is_transition || prev_handle_is_transition)) {
col[0] = 0.5f;
col[2] = 0.4f;
}
}
}
else {
immUniformColor4f(0.65f, 0.65f, 0.65f, 1.0f);
mul_v3_fl(col, 0.65f);
}
immUniformColor4fv(col);
immBegin(GPU_PRIM_TRI_FAN, 3);
immVertex2f(pos, handle_position - ui_triangle_size / 2, bottom);
immVertex2f(pos, handle_position + ui_triangle_size / 2, bottom);
@ -385,10 +406,21 @@ static void retime_speed_text_draw(const bContext *C,
return; /* Label out of strip bounds. */
}
const float speed = SEQ_retiming_handle_speed_get(seq, next_handle);
char label_str[40];
size_t label_len;
char label_str[20];
const size_t label_len = SNPRINTF_RLEN(label_str, "%d%%", round_fl_to_int(speed * 100.0f));
if (SEQ_retiming_handle_is_transition_type(handle)) {
const float prev_speed = SEQ_retiming_handle_speed_get(seq, handle - 1);
const float next_speed = SEQ_retiming_handle_speed_get(seq, next_handle + 1);
label_len = SNPRINTF_RLEN(label_str,
"%d%% - %d%%",
round_fl_to_int(prev_speed * 100.0f),
round_fl_to_int(next_speed * 100.0f));
}
else {
const float speed = SEQ_retiming_handle_speed_get(seq, next_handle);
label_len = SNPRINTF_RLEN(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));
@ -410,9 +442,15 @@ static void retime_speed_text_draw(const bContext *C,
static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz)
{
const RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz;
RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz;
const View2D *v2d = UI_view2d_fromcontext(C);
/* TODO: This is hardcoded behavior, same as preselect gizmos in 3D view.
* Better solution would be to check operator keymap and display this information in status bar
* and tooltip. */
wmEvent *event = CTX_wm_window(C)->eventstate;
gizmo->create_transition_operation = (event->modifier & KM_SHIFT) != 0;
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);

View File

@ -165,7 +165,12 @@ static int sequencer_retiming_handle_move_invoke(bContext *C, wmOperator *op, co
return OPERATOR_CANCELLED;
}
op->customdata = handle;
RNA_int_set(op->ptr, "handle_index", SEQ_retiming_handle_index_get(seq, handle));
if ((event->modifier & KM_SHIFT) != 0) {
op->customdata = (void *)1;
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
@ -178,19 +183,64 @@ static int sequencer_retiming_handle_move_modal(bContext *C, wmOperator *op, con
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
int handle_index = RNA_int_get(op->ptr, "handle_index");
SeqRetimingHandle *handle = &SEQ_retiming_handles_get(seq)[handle_index];
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_retiming_handle_timeline_frame_get(scene, seq, handle_prev) + 1;
mouse_x = max_ff(xmin, mouse_x);
offset = mouse_x - SEQ_retiming_handle_timeline_frame_get(scene, seq, handle);
if (offset == 0) {
return OPERATOR_RUNNING_MODAL;
}
/* Add retiming gradient and move handle. */
if (op->customdata) {
SeqRetimingHandle *transition_handle = SEQ_retiming_add_transition(
scene, seq, handle, abs(offset));
/* New gradient handle was created - update operator properties. */
if (transition_handle != nullptr) {
if (offset < 0) {
handle = transition_handle;
}
else {
handle = transition_handle + 1;
}
RNA_int_set(op->ptr, "handle_index", SEQ_retiming_handle_index_get(seq, handle));
}
}
const bool handle_is_transition = SEQ_retiming_handle_is_transition_type(handle);
const bool prev_handle_is_transition = SEQ_retiming_handle_is_transition_type(handle - 1);
/* When working with transiton, change handles when moving past pivot point. */
if (handle_is_transition || prev_handle_is_transition) {
SeqRetimingHandle *transition_start, *transition_end;
if (handle_is_transition) {
transition_start = handle;
transition_end = handle + 1;
}
else {
transition_start = handle - 1;
transition_end = handle;
}
const int offset_l = mouse_x -
SEQ_retiming_handle_timeline_frame_get(scene, seq, transition_start);
const int offset_r = mouse_x -
SEQ_retiming_handle_timeline_frame_get(scene, seq, transition_end);
if (prev_handle_is_transition && offset_l < 0) {
RNA_int_set(
op->ptr, "handle_index", SEQ_retiming_handle_index_get(seq, transition_start));
}
if (handle_is_transition && offset_r > 0) {
RNA_int_set(op->ptr, "handle_index", SEQ_retiming_handle_index_get(seq, transition_end));
}
}
SEQ_retiming_offset_handle(scene, seq, handle, offset);
SEQ_relations_invalidate_cache_raw(scene, seq);
@ -264,6 +314,14 @@ static int sequesequencer_retiming_handle_add_exec(bContext *C, wmOperator *op)
timeline_frame = BKE_scene_frame_get(scene);
}
const int frame_index = BKE_scene_frame_get(scene) - SEQ_time_start_frame_get(seq);
const SeqRetimingHandle *handle = SEQ_retiming_find_segment_start_handle(seq, frame_index);
if (SEQ_retiming_handle_is_transition_type(handle)) {
BKE_report(op->reports, RPT_ERROR, "Can not create handle inside of speed transition");
return OPERATOR_CANCELLED;
}
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) {
@ -314,7 +372,7 @@ static int sequencer_retiming_handle_remove_exec(bContext *C, wmOperator *op)
Sequence *seq = ed->act_seq;
SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata;
SEQ_retiming_remove_handle(seq, handle);
SEQ_retiming_remove_handle(scene, seq, handle);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;

View File

@ -121,10 +121,18 @@ typedef struct Strip {
ColorManagedColorspaceSettings colorspace_settings;
} Strip;
typedef enum eSeqRetimingHandleFlag {
SPEED_TRANSITION = (1 << 0),
} eSeqRetimingHandleFlag;
typedef struct SeqRetimingHandle {
int strip_frame_index;
int _pad0[2];
int flag; /* eSeqRetimingHandleFlag */
int _pad0;
float retiming_factor; /* Value between 0-1 mapped to original content range. */
int original_strip_frame_index; /* Used for transition handles only. */
float original_retiming_factor; /* Used for transition handles only. */
} SeqRetimingHandle;
typedef struct SequenceRuntime {

View File

@ -329,7 +329,7 @@ static void rna_Sequence_retiming_handle_remove(ID *id, SeqRetimingHandle *handl
return;
}
SEQ_retiming_remove_handle(seq, handle);
SEQ_retiming_remove_handle(scene, seq, handle);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL);

View File

@ -26,11 +26,17 @@ bool SEQ_retiming_is_allowed(const struct Sequence *seq);
* This function always reallocates memory, so when function is used all stored pointers will
* become invalid.
*/
struct SeqRetimingHandle *SEQ_retiming_add_handle(struct Scene *scene,
struct SeqRetimingHandle *SEQ_retiming_add_handle(const struct Scene *scene,
struct Sequence *seq,
const int timeline_frame);
SeqRetimingHandle *SEQ_retiming_add_transition(const struct Scene *scene,
struct Sequence *seq,
struct SeqRetimingHandle *handle,
const int offset);
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_remove_handle(const struct Scene *scene,
struct Sequence *seq,
struct SeqRetimingHandle *handle);
void SEQ_retiming_offset_handle(const struct Scene *scene,
struct Sequence *seq,
struct SeqRetimingHandle *handle,
@ -43,6 +49,9 @@ void SEQ_retiming_sound_animation_data_set(const struct Scene *scene, const stru
float SEQ_retiming_handle_timeline_frame_get(const struct Scene *scene,
const struct Sequence *seq,
const struct SeqRetimingHandle *handle);
const SeqRetimingHandle *SEQ_retiming_find_segment_start_handle(const struct Sequence *seq,
const int frame_index);
bool SEQ_retiming_handle_is_transition_type(const struct SeqRetimingHandle *handle);
#ifdef __cplusplus
}
#endif

View File

@ -64,8 +64,8 @@ static bool seq_retiming_is_last_handle(const Sequence *seq, const SeqRetimingHa
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 *SEQ_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)) {
@ -128,40 +128,136 @@ bool SEQ_retiming_is_allowed(const Sequence *seq)
SEQ_TYPE_MASK);
}
float seq_retiming_evaluate(const Sequence *seq, const float frame_index)
static int seq_retiming_segment_length_get(const SeqRetimingHandle *start_handle)
{
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);
UNUSED_VARS_NDEBUG(previous_handle_index);
if (next_handle == nullptr) {
return 1.0f;
}
const int segment_length = next_handle->strip_frame_index - previous_handle->strip_frame_index;
const float 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);
const SeqRetimingHandle *end_handle = start_handle + 1;
return end_handle->strip_frame_index - start_handle->strip_frame_index;
}
SeqRetimingHandle *SEQ_retiming_add_handle(Scene *scene, Sequence *seq, const int timeline_frame)
static float seq_retiming_segment_step_get(const SeqRetimingHandle *start_handle)
{
const SeqRetimingHandle *end_handle = start_handle + 1;
const int segment_length = seq_retiming_segment_length_get(start_handle);
const float segment_fac_diff = end_handle->retiming_factor - start_handle->retiming_factor;
return segment_fac_diff / segment_length;
}
static void seq_retiming_segment_as_line_segment(const SeqRetimingHandle *start_handle,
double r_v1[2],
double r_v2[2])
{
const SeqRetimingHandle *end_handle = start_handle + 1;
r_v1[0] = start_handle->strip_frame_index;
r_v1[1] = start_handle->retiming_factor;
r_v2[0] = end_handle->strip_frame_index;
r_v2[1] = end_handle->retiming_factor;
}
static void seq_retiming_line_segments_tangent_circle(const SeqRetimingHandle *start_handle,
double r_center[2],
double *radius)
{
double s1_1[2], s1_2[2], s2_1[2], s2_2[2], p1_2[2];
/* Get 2 segments. */
seq_retiming_segment_as_line_segment(start_handle - 1, s1_1, s1_2);
seq_retiming_segment_as_line_segment(start_handle + 1, s2_1, s2_2);
/* Backup first segment end point - needed to calculate arc radius. */
copy_v2_v2_db(p1_2, s1_2);
/* Convert segments to vectors. */
double v1[2], v2[2];
sub_v2_v2v2_db(v1, s1_1, s1_2);
sub_v2_v2v2_db(v2, s2_1, s2_2);
/* Rotate segments by 90 degrees around seg. 1 end and seg. 2 start point. */
SWAP(double, v1[0], v1[1]);
SWAP(double, v2[0], v2[1]);
v1[0] *= -1;
v2[0] *= -1;
copy_v2_v2_db(s1_1, s1_2);
add_v2_v2_db(s1_2, v1);
copy_v2_v2_db(s2_2, s2_1);
add_v2_v2_db(s2_2, v2);
/* Get center and radius of arc segment between 2 linear segments. */
double lambda, mu;
isect_seg_seg_v2_lambda_mu_db(s1_1, s1_2, s2_1, s2_2, &lambda, &mu);
r_center[0] = s1_1[0] + lambda * (s1_2[0] - s1_1[0]);
r_center[1] = s1_1[1] + lambda * (s1_2[1] - s1_1[1]);
*radius = len_v2v2_db(p1_2, r_center);
}
bool SEQ_retiming_handle_is_transition_type(const SeqRetimingHandle *handle)
{
return (handle->flag & SPEED_TRANSITION) != 0;
}
/* Check colinearity of 2 segments allowing for some imprecision.
* `isect_seg_seg_v2_lambda_mu_db()` return value does not work well in this case. */
static bool seq_retiming_transition_is_linear(const Sequence *seq, const SeqRetimingHandle *handle)
{
const float prev_speed = SEQ_retiming_handle_speed_get(seq, handle - 1);
const float next_speed = SEQ_retiming_handle_speed_get(seq, handle + 1);
return abs(prev_speed - next_speed) < 0.01f;
}
static float seq_retiming_evaluate_arc_segment(const SeqRetimingHandle *handle,
const float frame_index)
{
double c[2], r;
seq_retiming_line_segments_tangent_circle(handle, c, &r);
const int side = c[1] > handle->retiming_factor ? -1 : 1;
const float y = c[1] + side * sqrt(pow(r, 2) - pow((frame_index - c[0]), 2));
return y;
}
float seq_retiming_evaluate(const Sequence *seq, const float frame_index)
{
const SeqRetimingHandle *start_handle = SEQ_retiming_find_segment_start_handle(seq, frame_index);
const int start_handle_index = start_handle - seq->retiming_handles;
BLI_assert(start_handle_index < seq->retiming_handle_num);
const float segment_frame_index = frame_index - start_handle->strip_frame_index;
if (!SEQ_retiming_handle_is_transition_type(start_handle)) {
const float segment_step = seq_retiming_segment_step_get(start_handle);
return start_handle->retiming_factor + segment_step * segment_frame_index;
}
if (seq_retiming_transition_is_linear(seq, start_handle)) {
const float segment_step = seq_retiming_segment_step_get(start_handle - 1);
return start_handle->retiming_factor + segment_step * segment_frame_index;
}
/* Sanity check for transition type. */
BLI_assert(start_handle_index > 0);
BLI_assert(start_handle_index < seq->retiming_handle_num - 1);
UNUSED_VARS_NDEBUG(start_handle_index);
return seq_retiming_evaluate_arc_segment(start_handle, frame_index);
}
SeqRetimingHandle *SEQ_retiming_add_handle(const Scene *scene,
Sequence *seq,
const int timeline_frame)
{
float frame_index = (timeline_frame - SEQ_time_start_frame_get(seq)) *
seq_time_media_playback_rate_factor_get(scene, 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) {
const SeqRetimingHandle *start_handle = SEQ_retiming_find_segment_start_handle(seq, frame_index);
if (start_handle->strip_frame_index == frame_index) {
return nullptr; /* Retiming handle already exists. */
}
if (SEQ_retiming_handle_is_transition_type(start_handle)) {
return nullptr;
}
SeqRetimingHandle *handles = seq->retiming_handles;
size_t handle_count = SEQ_retiming_handles_count(seq);
const int new_handle_index = closest_handle - handles + 1;
const int new_handle_index = start_handle - handles + 1;
BLI_assert(new_handle_index >= 0);
BLI_assert(new_handle_index < handle_count);
@ -186,6 +282,81 @@ SeqRetimingHandle *SEQ_retiming_add_handle(Scene *scene, Sequence *seq, const in
return added_handle;
}
static void seq_retiming_offset_linear_handle(const Scene *scene,
Sequence *seq,
SeqRetimingHandle *handle,
const int offset)
{
MutableSpan handles = SEQ_retiming_handles_get(seq);
for (SeqRetimingHandle *next_handle = handle; next_handle < handles.end(); next_handle++) {
next_handle->strip_frame_index += offset * seq_time_media_playback_rate_factor_get(scene, seq);
}
/* Handle affected transitions: remove and re-create transition. This way transition won't change
* length.
* Alternative solution is to find where in arc segment the `y` value is closest to `handle`
* retiming factor, then trim transition to that point. This would change transition length. */
if (SEQ_retiming_handle_is_transition_type(handle - 2)) {
SeqRetimingHandle *transition_handle = handle - 2;
const int transition_offset = transition_handle->strip_frame_index -
transition_handle->original_strip_frame_index;
const int transition_handle_index = SEQ_retiming_handle_index_get(seq, transition_handle);
SEQ_retiming_remove_handle(scene, seq, transition_handle);
SeqRetimingHandle *orig_handle = seq->retiming_handles + transition_handle_index;
SEQ_retiming_add_transition(scene, seq, orig_handle, -transition_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));
}
static void seq_retiming_offset_transition_handle(const Scene *scene,
const Sequence *seq,
SeqRetimingHandle *handle,
const int offset)
{
SeqRetimingHandle *handle_start, *handle_end;
int corrected_offset;
if (SEQ_retiming_handle_is_transition_type(handle)) {
handle_start = handle;
handle_end = handle + 1;
corrected_offset = offset;
}
else {
handle_start = handle - 1;
handle_end = handle;
corrected_offset = -1 * offset;
}
/* Prevent transition handles crossing each other. */
const float start_frame = SEQ_retiming_handle_timeline_frame_get(scene, seq, handle_start);
const float end_frame = SEQ_retiming_handle_timeline_frame_get(scene, seq, handle_end);
int xmax = ((start_frame + end_frame) / 2) - 1;
int max_offset = xmax - start_frame;
corrected_offset = min_ii(corrected_offset, max_offset);
/* Prevent mirrored movement crossing any handle. */
SeqRetimingHandle *prev_segment_end = handle_start - 1, *next_segment_start = handle_end + 1;
const int offset_min_left = SEQ_retiming_handle_timeline_frame_get(
scene, seq, prev_segment_end) +
1 - start_frame;
const int offset_min_right =
end_frame - SEQ_retiming_handle_timeline_frame_get(scene, seq, next_segment_start) - 1;
corrected_offset = max_iii(corrected_offset, offset_min_left, offset_min_right);
const float prev_segment_step = seq_retiming_segment_step_get(handle_start - 1);
const float next_segment_step = seq_retiming_segment_step_get(handle_end);
handle_start->strip_frame_index += corrected_offset;
handle_start->retiming_factor += corrected_offset * prev_segment_step;
handle_end->strip_frame_index -= corrected_offset;
handle_end->retiming_factor -= corrected_offset * next_segment_step;
}
void SEQ_retiming_offset_handle(const Scene *scene,
Sequence *seq,
SeqRetimingHandle *handle,
@ -195,16 +366,36 @@ void SEQ_retiming_offset_handle(const Scene *scene,
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_media_playback_rate_factor_get(scene, seq);
}
SeqRetimingHandle *prev_handle = handle - 1;
SeqRetimingHandle *next_handle = handle + 1;
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));
/* Limit retiming handle movement. */
int corrected_offset = offset;
int handle_frame = SEQ_retiming_handle_timeline_frame_get(scene, seq, handle);
int offset_min = SEQ_retiming_handle_timeline_frame_get(scene, seq, prev_handle) + 1 -
handle_frame;
int offset_max;
if (SEQ_retiming_handle_index_get(seq, handle) == seq->retiming_handle_num - 1) {
offset_max = INT_MAX;
}
else {
offset_max = SEQ_retiming_handle_timeline_frame_get(scene, seq, next_handle) - 1 -
handle_frame;
}
corrected_offset = max_ii(corrected_offset, offset_min);
corrected_offset = min_ii(corrected_offset, offset_max);
if (SEQ_retiming_handle_is_transition_type(handle) ||
SEQ_retiming_handle_is_transition_type(prev_handle))
{
seq_retiming_offset_transition_handle(scene, seq, handle, corrected_offset);
}
else {
seq_retiming_offset_linear_handle(scene, seq, handle, corrected_offset);
}
}
void SEQ_retiming_remove_handle(Sequence *seq, SeqRetimingHandle *handle)
static void seq_retiming_remove_handle_ex(Sequence *seq, SeqRetimingHandle *handle)
{
SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq);
if (handle->strip_frame_index == 0 || handle == last_handle) {
@ -225,6 +416,64 @@ void SEQ_retiming_remove_handle(Sequence *seq, SeqRetimingHandle *handle)
seq->retiming_handle_num--;
}
/* This function removes transition segment and creates retiming handle where it originally was. */
static void seq_retiming_remove_transition(const Scene *scene,
Sequence *seq,
SeqRetimingHandle *handle)
{
const int orig_frame_index = handle->original_strip_frame_index;
const float orig_retiming_factor = handle->original_retiming_factor;
/* Remove both handles defining transition. */
int handle_index = SEQ_retiming_handle_index_get(seq, handle);
seq_retiming_remove_handle_ex(seq, handle);
seq_retiming_remove_handle_ex(seq, seq->retiming_handles + handle_index);
/* Create original linear handle. */
SeqRetimingHandle *orig_handle = SEQ_retiming_add_handle(
scene, seq, SEQ_time_start_frame_get(seq) + orig_frame_index);
orig_handle->retiming_factor = orig_retiming_factor;
}
void SEQ_retiming_remove_handle(const Scene *scene, Sequence *seq, SeqRetimingHandle *handle)
{
if (SEQ_retiming_handle_is_transition_type(handle)) {
seq_retiming_remove_transition(scene, seq, handle);
return;
}
SeqRetimingHandle *previous_handle = handle - 1;
if (SEQ_retiming_handle_is_transition_type(previous_handle)) {
seq_retiming_remove_transition(scene, seq, previous_handle);
return;
}
seq_retiming_remove_handle_ex(seq, handle);
}
SeqRetimingHandle *SEQ_retiming_add_transition(const Scene *scene,
Sequence *seq,
SeqRetimingHandle *handle,
const int offset)
{
if (SEQ_retiming_handle_is_transition_type(handle) ||
SEQ_retiming_handle_is_transition_type(handle - 1))
{
return nullptr;
}
const int orig_handle_index = SEQ_retiming_handle_index_get(seq, handle);
const int orig_timeline_frame = SEQ_retiming_handle_timeline_frame_get(scene, seq, handle);
const int orig_frame_index = handle->strip_frame_index;
const float orig_retiming_factor = handle->retiming_factor;
SEQ_retiming_add_handle(scene, seq, orig_timeline_frame + offset);
SeqRetimingHandle *transition_handle = SEQ_retiming_add_handle(
scene, seq, orig_timeline_frame - offset);
transition_handle->flag |= SPEED_TRANSITION;
transition_handle->original_strip_frame_index = orig_frame_index;
transition_handle->original_retiming_factor = orig_retiming_factor;
seq_retiming_remove_handle_ex(seq, seq->retiming_handles + orig_handle_index + 1);
return seq->retiming_handles + orig_handle_index;
}
float SEQ_retiming_handle_speed_get(const Sequence *seq, const SeqRetimingHandle *handle)
{
if (handle->strip_frame_index == 0) {
@ -246,22 +495,123 @@ float SEQ_retiming_handle_speed_get(const Sequence *seq, const SeqRetimingHandle
return speed;
}
enum eRangeType {
LINEAR = 0,
TRANSITION = 1,
};
enum eIntersectType {
FULL,
PARTIAL_START,
PARTIAL_END,
INSIDE,
NONE,
};
class RetimingRange {
public:
int start, end;
float speed;
blender::Vector<float> speed_table;
enum eIntersectType {
FULL,
PARTIAL_START,
PARTIAL_END,
INSIDE,
NONE,
};
RetimingRange(int start_frame, int end_frame, float speed)
: start(start_frame), end(end_frame), speed(speed)
eRangeType type;
RetimingRange(const Sequence *seq, int start_frame, int end_frame, float speed, eRangeType type)
: start(start_frame), end(end_frame), speed(speed), type(type)
{
if (type == TRANSITION) {
speed = 1.0f;
claculate_speed_table_from_seq(seq);
}
}
RetimingRange(int start_frame, int end_frame, float speed, eRangeType type)
: start(start_frame), end(end_frame), speed(speed), type(type)
{
}
RetimingRange duplicate()
{
RetimingRange new_range = RetimingRange(start, end, speed, type);
for (int i = 0; i < speed_table.size(); i++) {
new_range.speed_table.append(speed_table[i]);
}
return new_range;
}
/* Create new range representing overlap of 2 ranges.
* Returns overlapping range. */
RetimingRange operator*(const RetimingRange rhs_range)
{
RetimingRange new_range = RetimingRange(0, 0, 0, LINEAR);
/* Offsets to merge speed tables. */
int range_offset = 0, rhs_range_offset = 0;
if (intersect_type(rhs_range) == FULL) {
new_range.start = start;
new_range.end = end;
rhs_range_offset = start - rhs_range.start;
}
else if (intersect_type(rhs_range) == PARTIAL_START) {
new_range.start = start;
new_range.end = rhs_range.end;
rhs_range_offset = start - rhs_range.start;
}
else if (intersect_type(rhs_range) == PARTIAL_END) {
new_range.start = rhs_range.start;
new_range.end = end;
range_offset = rhs_range.start - start;
}
else if (intersect_type(rhs_range) == INSIDE) {
new_range.start = rhs_range.start;
new_range.end = rhs_range.end;
range_offset = rhs_range.start - start;
}
if (type != TRANSITION && rhs_range.type != TRANSITION) {
new_range.speed = speed * rhs_range.speed;
return new_range;
}
/* One of ranges is transition type, so speed tables has to be copied. */
new_range.type = TRANSITION;
new_range.speed = 1.0f;
const int new_range_len = new_range.end - new_range.start;
if (type == TRANSITION && rhs_range.type == TRANSITION) {
for (int i = 0; i < new_range_len; i++) {
const float range_speed = speed_table[i + range_offset];
const float rhs_range_speed = rhs_range.speed_table[i + rhs_range_offset];
new_range.speed_table.append(range_speed * rhs_range_speed);
}
}
else if (type == TRANSITION) {
for (int i = 0; i < new_range_len; i++) {
const float range_speed = speed_table[i + range_offset];
new_range.speed_table.append(range_speed * rhs_range.speed);
}
}
else if (rhs_range.type == TRANSITION) {
for (int i = 0; i < new_range_len; i++) {
const float rhs_range_speed = rhs_range.speed_table[i + rhs_range_offset];
new_range.speed_table.append(speed * rhs_range_speed);
}
}
return new_range;
}
void claculate_speed_table_from_seq(const Sequence *seq)
{
for (int frame = start; frame <= end; frame++) {
/* We need number actual number of frames here. */
const double normal_step = 1 / (double)seq->len;
/* Who needs calculus, when you can have slow code? */
const double val_prev = seq_retiming_evaluate(seq, frame - 1);
const double val = seq_retiming_evaluate(seq, frame);
const double speed_at_frame = (val - val_prev) / normal_step;
speed_table.append(speed_at_frame);
}
}
const eIntersectType intersect_type(const RetimingRange &other) const
@ -297,7 +647,8 @@ class RetimingRangeData {
int frame_start = SEQ_time_start_frame_get(seq) + handle_prev->strip_frame_index;
int frame_end = SEQ_time_start_frame_get(seq) + handle.strip_frame_index;
RetimingRange range = RetimingRange(frame_start, frame_end, speed);
eRangeType type = SEQ_retiming_handle_is_transition_type(handle_prev) ? TRANSITION : LINEAR;
RetimingRange range = RetimingRange(seq, frame_start, frame_end, speed, type);
ranges.append(range);
}
}
@ -306,7 +657,8 @@ class RetimingRangeData {
{
if (ranges.is_empty()) {
for (const RetimingRange &rhs_range : rhs.ranges) {
RetimingRange range = RetimingRange(rhs_range.start, rhs_range.end, rhs_range.speed);
RetimingRange range = RetimingRange(
rhs_range.start, rhs_range.end, rhs_range.speed, rhs_range.type);
ranges.append(range);
}
return *this;
@ -315,31 +667,30 @@ class RetimingRangeData {
for (int i = 0; i < ranges.size(); i++) {
RetimingRange &range = ranges[i];
for (const RetimingRange &rhs_range : rhs.ranges) {
if (range.intersect_type(rhs_range) == range.NONE) {
if (range.intersect_type(rhs_range) == NONE) {
continue;
}
else if (range.intersect_type(rhs_range) == range.FULL) {
range.speed *= rhs_range.speed;
else if (range.intersect_type(rhs_range) == FULL) {
RetimingRange isect = range * rhs_range;
ranges.remove(i);
ranges.insert(i, isect);
}
else if (range.intersect_type(rhs_range) == range.PARTIAL_START) {
RetimingRange range_left = RetimingRange(
range.start, rhs_range.end, range.speed * rhs_range.speed);
if (range.intersect_type(rhs_range) == PARTIAL_START) {
ranges.insert(i, range * rhs_range);
ranges.insert(i, range * rhs_range);
range.start = rhs_range.end + 1;
ranges.insert(i, range_left);
}
else if (range.intersect_type(rhs_range) == range.PARTIAL_END) {
RetimingRange range_left = RetimingRange(range.start, rhs_range.start - 1, range.speed);
range.start = rhs_range.start;
ranges.insert(i, range_left);
else if (range.intersect_type(rhs_range) == PARTIAL_END) {
ranges.insert(i, range * rhs_range);
range.end = rhs_range.start;
}
else if (range.intersect_type(rhs_range) == range.INSIDE) {
RetimingRange range_left = RetimingRange(range.start, rhs_range.start - 1, range.speed);
RetimingRange range_mid = RetimingRange(
rhs_range.start, rhs_range.start, rhs_range.speed * range.speed);
else if (range.intersect_type(rhs_range) == INSIDE) {
RetimingRange left_range = range.duplicate();
left_range.end = rhs_range.start;
range.start = rhs_range.end + 1;
ranges.insert(i, range_left);
ranges.insert(i, range_mid);
break;
ranges.insert(i, left_range);
ranges.insert(i, range * rhs_range);
}
}
}
@ -364,9 +715,21 @@ static RetimingRangeData seq_retiming_range_data_get(const Scene *scene, const S
void SEQ_retiming_sound_animation_data_set(const Scene *scene, const Sequence *seq)
{
RetimingRangeData retiming_data = seq_retiming_range_data_get(scene, seq);
for (const RetimingRange &range : retiming_data.ranges) {
BKE_sound_set_scene_sound_pitch_constant_range(
seq->scene_sound, range.start, range.end, range.speed);
for (int i = 0; i < retiming_data.ranges.size(); i++) {
RetimingRange range = retiming_data.ranges[i];
if (range.type == TRANSITION) {
const int range_length = range.end - range.start;
for (int i = 0; i <= range_length; i++) {
const int frame = range.start + i;
BKE_sound_set_scene_sound_pitch_at_frame(
seq->scene_sound, frame, range.speed_table[i], true);
}
}
else {
BKE_sound_set_scene_sound_pitch_constant_range(
seq->scene_sound, range.start, range.end, range.speed);
}
}
}