Cleanup: VSE waveform drawing

No functional changes.
This commit is contained in:
Richard Antalik 2022-07-15 15:46:50 +02:00
parent 011d3c75a7
commit 82f65d8971
1 changed files with 251 additions and 293 deletions

View File

@ -241,329 +241,296 @@ typedef struct WaveVizData {
float pos[2];
float rms_pos;
bool clip;
bool end;
bool draw_line; /* Draw triangle otherwise. */
bool final_sample; /* There are no more samples. */
} WaveVizData;
static int get_section_len(WaveVizData *start, WaveVizData *end)
static bool seq_draw_waveforms_poll(const bContext *C, SpaceSeq *sseq, Sequence *seq)
{
int len = 0;
while (start != end) {
len++;
if (start->end) {
return len;
}
start++;
const bool strip_is_valid = seq->type == SEQ_TYPE_SOUND_RAM && seq->sound != NULL;
const bool overlays_enabled = (sseq->flag & SEQ_SHOW_OVERLAY) != 0;
const bool ovelay_option = ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) != 0 ||
(seq->flag & SEQ_AUDIO_DRAW_WAVEFORM));
if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) != 0) {
return false;
}
return len;
if (strip_is_valid && overlays_enabled && ovelay_option) {
return true;
}
return false;
}
static void draw_waveform(WaveVizData *iter, WaveVizData *end, GPUPrimType prim_type, bool use_rms)
static void waveform_job_start_if_needed(const bContext *C, Sequence *seq)
{
int strip_len = get_section_len(iter, end);
if (strip_len > 1) {
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
bSound *sound = seq->sound;
immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
immBegin(prim_type, strip_len);
while (iter != end) {
if (iter->clip) {
immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f);
}
else if (use_rms) {
immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f);
}
else {
immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f);
}
if (use_rms) {
immVertex2f(pos, iter->pos[0], iter->rms_pos);
}
else {
immVertex2f(pos, iter->pos[0], iter->pos[1]);
}
if (iter->end) {
/* End of line. */
iter++;
strip_len = get_section_len(iter, end);
if (strip_len != 0) {
immEnd();
immUnbindProgram();
immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
immBegin(prim_type, strip_len);
}
}
else {
iter++;
}
BLI_spin_lock(sound->spinlock);
if (!sound->waveform) {
/* Load the waveform data if it hasn't been loaded and cached already. */
if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) {
/* Prevent sounds from reloading. */
sound->tags |= SOUND_TAGS_WAVEFORM_LOADING;
BLI_spin_unlock(sound->spinlock);
sequencer_preview_add_sound(C, seq);
}
immEnd();
immUnbindProgram();
else {
BLI_spin_unlock(sound->spinlock);
}
}
BLI_spin_unlock(sound->spinlock);
}
GPU_blend(GPU_BLEND_NONE);
static size_t get_vertex_count(WaveVizData *waveform_data)
{
bool draw_line = waveform_data->draw_line;
size_t length = 0;
while (waveform_data->draw_line == draw_line && !waveform_data->final_sample) {
waveform_data++;
length++;
}
return length;
}
static size_t draw_waveform_segment(WaveVizData *waveform_data, bool use_rms)
{
size_t vertices_done = 0;
size_t vertex_count = get_vertex_count(waveform_data);
/* Not enough data to draw. */
if (vertex_count <= 2) {
return vertex_count;
}
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
GPUPrimType prim_type = waveform_data->draw_line ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
immBegin(prim_type, vertex_count);
while (vertices_done < vertex_count && !waveform_data->final_sample) {
/* Color. */
if (waveform_data->clip) {
immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f);
}
else if (use_rms) {
immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f);
}
else {
immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f);
}
/* Vertices. */
if (use_rms) {
immVertex2f(pos, waveform_data->pos[0], waveform_data->rms_pos);
}
else {
immVertex2f(pos, waveform_data->pos[0], waveform_data->pos[1]);
}
vertices_done++;
waveform_data++;
}
immEnd();
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
return vertices_done;
}
static void draw_waveform(WaveVizData *waveform_data, size_t wave_data_len)
{
size_t items_done = 0;
while (items_done < wave_data_len) {
if (!waveform_data[items_done].draw_line) { /* Draw RMS. */
draw_waveform_segment(&waveform_data[items_done], true);
}
items_done += draw_waveform_segment(&waveform_data[items_done], false);
}
}
static float clamp_frame_coord_to_pixel(float frame_coord,
float pixel_frac,
float frames_per_pixel)
static float align_frame_with_pixel(const View2D *v2d, float frame_coord, float frames_per_pixel)
{
float cur_pixel = (frame_coord / frames_per_pixel);
float new_pixel = (int)(frame_coord / frames_per_pixel) + pixel_frac;
if (cur_pixel > new_pixel) {
new_pixel += 1.0f;
return round_fl_to_int(frame_coord / frames_per_pixel) * frames_per_pixel;
}
static void write_waveform_data(WaveVizData *waveform_data,
const vec2f pos,
const float rms,
const bool is_clipping,
const bool draw_line)
{
waveform_data->pos[0] = pos.x;
waveform_data->pos[1] = pos.y;
waveform_data->clip = is_clipping;
waveform_data->rms_pos = rms;
waveform_data->draw_line = draw_line;
}
static size_t waveform_append_sample(WaveVizData *waveform_data,
vec2f pos,
const float value_min,
const float value_max,
const float y_mid,
const float y_scale,
const float rms,
const bool is_clipping,
const bool is_line_strip)
{
size_t data_written = 0;
pos.y = y_mid + value_min * y_scale;
float rms_value = y_mid + max_ff(-rms, value_min) * y_scale;
write_waveform_data(&waveform_data[0], pos, rms_value, is_clipping, is_line_strip);
data_written++;
/* Use `value_max` as second vertex for triangle drawing. */
if (!is_line_strip) {
pos.y = y_mid + value_max * y_scale;
rms_value = y_mid + min_ff(rms, value_max) * y_scale;
write_waveform_data(&waveform_data[1], pos, rms_value, is_clipping, is_line_strip);
data_written++;
}
return new_pixel * frames_per_pixel;
return data_written;
}
/**
* \param x1, x2, y1, y2: The starting and end X value to draw the wave, same for y1 and y2.
* \param frames_per_pixel: The amount of pixels a whole frame takes up (x-axis direction).
*/
static void draw_seq_waveform_overlay(View2D *v2d,
const bContext *C,
SpaceSeq *sseq,
Scene *scene,
Sequence *seq,
float x1,
float y1,
float x2,
float y2,
float frames_per_pixel)
static void draw_seq_waveform_overlay(
const bContext *C, ARegion *region, Sequence *seq, float x1, float y1, float x2, float y2)
{
if (seq->sound && ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) ||
(seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) {
/* Make sure that the start drawing position is aligned to the pixels on the screen to avoid
* flickering when moving around the strip.
* To do this we figure out the fractional offset in pixel space by checking where the
* window starts.
* We then append this pixel offset to our strip start coordinate to ensure we are aligned to
* the screen pixel grid. */
float pixel_frac = v2d->cur.xmin / frames_per_pixel - floor(v2d->cur.xmin / frames_per_pixel);
float x1_adj = clamp_frame_coord_to_pixel(x1, pixel_frac, frames_per_pixel);
const View2D *v2d = &region->v2d;
Scene *scene = CTX_data_scene(C);
/* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */
float x1_offset = max_ff(v2d->cur.xmin, x1_adj);
float x2_offset = min_ff(v2d->cur.xmax, x2);
const float frames_per_pixel = BLI_rctf_size_x(&region->v2d.cur) / region->winx;
const float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS;
float samples_per_pixel = samples_per_frame * frames_per_pixel;
/* Calculate how long the strip that is in view is in pixels. */
int pix_strip_len = round((x2_offset - x1_offset) / frames_per_pixel);
/* Align strip start with nearest pixel to prevent waveform flickering. */
const float x1_aligned = align_frame_with_pixel(v2d, x1, frames_per_pixel);
/* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */
const float frame_start = max_ff(v2d->cur.xmin, x1_aligned);
const float frame_end = min_ff(v2d->cur.xmax, x2);
const int pixels_to_draw = round_fl_to_int((frame_end - frame_start) / frames_per_pixel);
if (pix_strip_len < 2) {
return;
if (pixels_to_draw < 2) {
return; /* Not much to draw, exit before running job. */
}
waveform_job_start_if_needed(C, seq);
SoundWaveform *waveform = seq->sound->waveform;
if (waveform == NULL || waveform->length == 0) {
return; /* Waveform was not built. */
}
/* F-Curve lookup is quite expensive, so do this after precondition. */
FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL);
WaveVizData *waveform_data = MEM_callocN(sizeof(WaveVizData) * pixels_to_draw * 3, __func__);
size_t wave_data_len = 0;
/* Offset must be also aligned, otherwise waveform flickers when moving left handle. */
const float strip_offset = align_frame_with_pixel(
v2d, seq->startofs + seq->anim_startofs, frames_per_pixel);
float start_sample = strip_offset * samples_per_frame;
start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND;
/* Add off-screen part of strip to offset. */
start_sample += (frame_start - x1_aligned) * samples_per_frame;
for (int i = 0; i < pixels_to_draw; i++) {
float sample = start_sample + i * samples_per_pixel;
int sample_index = round_fl_to_int(sample);
if (sample_index < 0) {
continue;
}
bSound *sound = seq->sound;
if (sample_index >= waveform->length) {
break;
}
BLI_spin_lock(sound->spinlock);
if (!sound->waveform) {
/* Load the waveform data if it hasn't been loaded and cached already. */
if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) {
/* Prevent sounds from reloading. */
sound->tags |= SOUND_TAGS_WAVEFORM_LOADING;
BLI_spin_unlock(sound->spinlock);
sequencer_preview_add_sound(C, seq);
float value_min = waveform->data[sample_index * 3];
float value_max = waveform->data[sample_index * 3 + 1];
float rms = waveform->data[sample_index * 3 + 2];
if (sample_index + 1 < waveform->length) {
/* Use simple linear interpolation. */
float f = sample - sample_index;
value_min = (1.0f - f) * value_min + f * waveform->data[sample_index * 3 + 3];
value_max = (1.0f - f) * value_max + f * waveform->data[sample_index * 3 + 4];
rms = (1.0f - f) * rms + f * waveform->data[sample_index * 3 + 5];
if (samples_per_pixel > 1.0f) {
/* We need to sum up the values we skip over until the next step. */
float next_pos = sample + samples_per_pixel;
int end_idx = next_pos;
for (int j = sample_index + 1; (j < waveform->length) && (j < end_idx); j++) {
value_min = min_ff(value_min, waveform->data[j * 3]);
value_max = max_ff(value_max, waveform->data[j * 3 + 1]);
rms = max_ff(rms, waveform->data[j * 3 + 2]);
}
}
else {
BLI_spin_unlock(sound->spinlock);
}
return; /* Nothing to draw. */
}
BLI_spin_unlock(sound->spinlock);
SoundWaveform *waveform = sound->waveform;
/* Waveform could not be built. */
if (waveform->length == 0) {
return;
}
/* F-Curve lookup is quite expensive, so do this after precondition. */
FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL);
float volume = seq->volume;
if (fcu && !BKE_fcurve_is_empty(fcu)) {
float evaltime = frame_start + (i * frames_per_pixel);
volume = evaluate_fcurve(fcu, evaltime);
CLAMP_MIN(volume, 0.0f);
}
WaveVizData *tri_strip_arr = MEM_callocN(sizeof(*tri_strip_arr) * pix_strip_len * 2,
"tri_strip");
WaveVizData *line_strip_arr = MEM_callocN(sizeof(*line_strip_arr) * pix_strip_len,
"line_strip");
value_min *= volume;
value_max *= volume;
rms *= volume;
WaveVizData *tri_strip_iter = tri_strip_arr;
WaveVizData *line_strip_iter = line_strip_arr;
bool is_clipping = false;
if (value_max > 1 || value_min < -1) {
is_clipping = true;
CLAMP_MAX(value_max, 1.0f);
CLAMP_MIN(value_min, -1.0f);
}
bool is_line_strip = (value_max - value_min < 0.05f);
/* The y coordinate for the middle of the strip. */
float y_mid = (y1 + y2) / 2.0f;
/* The length from the middle of the strip to the top/bottom. */
float y_scale = (y2 - y1) / 2.0f;
float volume = seq->volume;
/* Value to keep track if the previous item to be drawn was a line strip. */
int8_t was_line_strip = -1; /* -1 == no previous value. */
float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS;
/* How many samples do we have for each pixel? */
float samples_per_pix = samples_per_frame * frames_per_pixel;
float strip_start_offset = seq->startofs + seq->anim_startofs;
float start_sample = 0;
if (strip_start_offset != 0) {
/* If start offset is not zero, we need to make sure that we pick the same start sample as if
* we simply scrolled the start of the strip off-screen. Otherwise we will get flickering
* when changing start offset as the pixel alignment will not be the same for the drawn
* samples. */
strip_start_offset = clamp_frame_coord_to_pixel(
x1 - strip_start_offset, pixel_frac, frames_per_pixel);
start_sample = fabsf(strip_start_offset - x1_adj) * samples_per_frame;
}
start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND;
/* If we scrolled the start off-screen, then the start sample should be at the first visible
* sample. */
start_sample += (x1_offset - x1_adj) * samples_per_frame;
for (int i = 0; i < pix_strip_len; i++) {
float sample_offset = start_sample + i * samples_per_pix;
int p = sample_offset;
if (p < 0) {
continue;
}
if (p >= waveform->length) {
break;
}
float value_min = waveform->data[p * 3];
float value_max = waveform->data[p * 3 + 1];
float rms = waveform->data[p * 3 + 2];
if (p + 1 < waveform->length) {
/* Use simple linear interpolation. */
float f = sample_offset - p;
value_min = (1.0f - f) * value_min + f * waveform->data[p * 3 + 3];
value_max = (1.0f - f) * value_max + f * waveform->data[p * 3 + 4];
rms = (1.0f - f) * rms + f * waveform->data[p * 3 + 5];
if (samples_per_pix > 1.0f) {
/* We need to sum up the values we skip over until the next step. */
float next_pos = sample_offset + samples_per_pix;
int end_idx = next_pos;
for (int j = p + 1; (j < waveform->length) && (j < end_idx); j++) {
value_min = min_ff(value_min, waveform->data[j * 3]);
value_max = max_ff(value_max, waveform->data[j * 3 + 1]);
rms = max_ff(rms, waveform->data[j * 3 + 2]);
}
}
}
if (fcu && !BKE_fcurve_is_empty(fcu)) {
float evaltime = x1_offset + (i * frames_per_pixel);
volume = evaluate_fcurve(fcu, evaltime);
CLAMP_MIN(volume, 0.0f);
}
value_min *= volume;
value_max *= volume;
rms *= volume;
bool clipping = false;
if (value_max > 1 || value_min < -1) {
clipping = true;
CLAMP_MAX(value_max, 1.0f);
CLAMP_MIN(value_min, -1.0f);
}
bool is_line_strip = (value_max - value_min < 0.05f);
if (!ELEM(was_line_strip, -1, is_line_strip)) {
/* If the previously added strip type isn't the same as the current one,
* add transition areas so they transition smoothly between each other. */
if (is_line_strip) {
/* This will be a line strip, end the tri strip. */
tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel;
tri_strip_iter->pos[1] = y_mid + value_min * y_scale;
tri_strip_iter->clip = clipping;
tri_strip_iter->rms_pos = tri_strip_iter->pos[1];
tri_strip_iter->end = true;
/* End of section. */
tri_strip_iter++;
/* Check if we are at the end.
* If so, skip one point line. */
if (i + 1 == pix_strip_len) {
continue;
}
}
else {
/* This will be a tri strip. */
line_strip_iter--;
tri_strip_iter->pos[0] = line_strip_iter->pos[0];
tri_strip_iter->pos[1] = line_strip_iter->pos[1];
tri_strip_iter->clip = line_strip_iter->clip;
tri_strip_iter->rms_pos = line_strip_iter->pos[1];
tri_strip_iter++;
/* Check if line had only one point. */
line_strip_iter--;
if (line_strip_iter < line_strip_arr || line_strip_iter->end) {
/* Only one point, skip it. */
line_strip_iter++;
}
else {
/* End of section. */
line_strip_iter++;
line_strip_iter->end = true;
line_strip_iter++;
}
}
}
was_line_strip = is_line_strip;
if (is_line_strip) {
line_strip_iter->pos[0] = x1_offset + i * frames_per_pixel;
line_strip_iter->pos[1] = y_mid + value_min * y_scale;
line_strip_iter->clip = clipping;
line_strip_iter++;
}
else {
tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel;
tri_strip_iter->pos[1] = y_mid + value_min * y_scale;
tri_strip_iter->clip = clipping;
tri_strip_iter->rms_pos = y_mid + max_ff(-rms, value_min) * y_scale;
tri_strip_iter++;
tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel;
tri_strip_iter->pos[1] = y_mid + value_max * y_scale;
tri_strip_iter->clip = clipping;
tri_strip_iter->rms_pos = y_mid + min_ff(rms, value_max) * y_scale;
tri_strip_iter++;
}
}
WaveVizData *tri_strip_end = tri_strip_iter;
WaveVizData *line_strip_end = line_strip_iter;
tri_strip_iter = tri_strip_arr;
line_strip_iter = line_strip_arr;
draw_waveform(line_strip_iter, line_strip_end, GPU_PRIM_LINE_STRIP, false);
draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, false);
draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, true);
MEM_freeN(tri_strip_arr);
MEM_freeN(line_strip_arr);
vec2f pos = {frame_start + i * frames_per_pixel, y_mid + value_min * y_scale};
WaveVizData *new_data = &waveform_data[wave_data_len];
wave_data_len += waveform_append_sample(
new_data, pos, value_min, value_max, y_mid, y_scale, rms, is_clipping, is_line_strip);
}
/* Terminate array, so `get_segment_length()` can know when to stop. */
waveform_data[wave_data_len].final_sample = true;
draw_waveform(waveform_data, wave_data_len);
MEM_freeN(waveform_data);
}
/*
static size_t *waveform_append(WaveVizData *waveform_data,
vec2f pos,
const float value_min,
const float value_max,
const float y_mid,
const float y_scale,
const float rms,
const bool is_clipping,
const bool is_line_strip)
*/
static void drawmeta_contents(Scene *scene,
Sequence *seqm,
float x1,
@ -1430,18 +1397,9 @@ static void draw_seq_strip(const bContext *C,
}
/* Draw sound strip waveform. */
if ((seq->type == SEQ_TYPE_SOUND_RAM) && ((sseq->flag & SEQ_SHOW_OVERLAY)) &&
(sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) == 0) {
draw_seq_waveform_overlay(v2d,
C,
sseq,
scene,
seq,
x1,
y_threshold ? y1 + 0.05f : y1,
x2,
y_threshold ? text_margin_y : y2,
BLI_rctf_size_x(&region->v2d.cur) / region->winx);
if (seq_draw_waveforms_poll(C, sseq, seq)) {
draw_seq_waveform_overlay(
C, region, seq, x1, y_threshold ? y1 + 0.05f : y1, x2, y_threshold ? text_margin_y : y2);
}
/* Draw locked state. */
if (SEQ_transform_is_locked(channels, seq)) {