diff --git a/source/blender/blenkernel/BKE_sound.h b/source/blender/blenkernel/BKE_sound.h index e644a0f93cc..ae6528b1ebd 100644 --- a/source/blender/blenkernel/BKE_sound.h +++ b/source/blender/blenkernel/BKE_sound.h @@ -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, diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c index 319ee05bad3..549e5fec02f 100644 --- a/source/blender/blenkernel/intern/sound.c +++ b/source/blender/blenkernel/intern/sound.c @@ -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, diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc index 26c5232bc97..08bb18252dc 100644 --- a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc @@ -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); diff --git a/source/blender/editors/space_sequencer/sequencer_retiming.cc b/source/blender/editors/space_sequencer/sequencer_retiming.cc index 7d01e5b25e1..bab13a7f9e8 100644 --- a/source/blender/editors/space_sequencer/sequencer_retiming.cc +++ b/source/blender/editors/space_sequencer/sequencer_retiming.cc @@ -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; diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index ad0b9b805b6..a7112df12c0 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -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 { diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 8591f35c051..011345a4306 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -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); diff --git a/source/blender/sequencer/SEQ_retiming.h b/source/blender/sequencer/SEQ_retiming.h index fff6551c8b2..45f912ef4e5 100644 --- a/source/blender/sequencer/SEQ_retiming.h +++ b/source/blender/sequencer/SEQ_retiming.h @@ -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 diff --git a/source/blender/sequencer/intern/strip_retiming.cc b/source/blender/sequencer/intern/strip_retiming.cc index 8427cb35629..5006b029eb4 100644 --- a/source/blender/sequencer/intern/strip_retiming.cc +++ b/source/blender/sequencer/intern/strip_retiming.cc @@ -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 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); + } } }