VSE: Smooth transition for retiming tool #107197
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue