Files
oak-editor/app/node/inputimmediate.cpp
T
Mike-Solar 2aa921b215 fix: bug-fix sweep across node, audio, render, plugin subsystems
Node core:
- MathNode/TrigonometryNode combo strings realigned with Operation enums
- mathbase scalar/vector operand pick no longer uses bitwise type checks
- NodeSetPositionAndDependenciesRecursively moves dependencies again
- RemoveAllKeyframes undo actually restores keyframes
- NodeGroup GetInputName null-deref guard, passthrough ids use input id
- NodeValueTable::Has is an exact type match; tag fallback only for
  empty tags; kStrCombo/kPushButton get data type names
- delete_all_keyframes no longer loops forever on unparented keyframes;
  keyframe-load failures propagate; rational interpolation falls back to
  double; OpacityEffect no longer leaks its internal MathNode

Audio/footage:
- AudioVisualWaveform: GetSummaryFromTime underflow OOB read, TrimIn
  prepend length bookkeeping, OverwriteSums source channel indexing
- PanNode inserts the pan value into the sample job (keyframed pan
  works); OutputParamsChanged is emitted on device change; PortAudio
  device indices are validated before Pa_GetDeviceInfo
- Footage: AdjustTimeByLoopMode no longer hangs/UBs on degenerate
  lengths, GetStreamIndex bounds-checked, CheckFootage clears stale
  state on missing files, failed probes are not cached,
  FootageDescription::Load requires its own root element

Render/track:
- ViewerOutput pushes the tagged samples value; TrackList disconnects
  the track-height lambda; GetTrackFromReference validity check
- RenderManager dummy backend: null-initialized threads, guarded
  decoder-cache/timer paths; Renderer::Destroy releases color cache
  shaders/textures; unknown dynamic backends no longer alias to oakgl
- SharedMemoryRegion POSIX attach validates segment size; ReadMessage
  skips blank lines instead of failing; GC counter clamped;
  IsRenderingCustomRange implemented; TimeOffsetNode gets a true
  inverse OutputTimeAdjustment; zero-speed clips return the held frame

Plugin/nodes:
- OliveClip: stored default region of definition is honored, on-demand
  images are cached; OliveHost sets host identity properties and logs
  instead of showing modal dialogs offscreen; Plugin.h dead decls gone
- DespillNode guards graph-less use with Rec.709 fallback; description
  typos fixed (despill, swirl); mosaic applies when only one axis
  matches; Windows-only Project filename separator test fixed
2026-07-17 08:26:48 +08:00

314 lines
6.9 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "inputimmediate.h"
#include "common/lerp.h"
#include "common/tohex.h"
#include "node.h"
namespace olive
{
NodeInputImmediate::NodeInputImmediate(NodeValue::Type type,
const SplitValue &default_val)
: default_value_(default_val)
, keyframing_(false)
{
set_data_type(type);
}
void NodeInputImmediate::set_standard_value_on_track(const QVariant &value,
int track)
{
standard_value_.replace(track, value);
}
void NodeInputImmediate::set_split_standard_value(const SplitValue &value)
{
for (int i = 0; i < value.size() && i < standard_value_.size(); i++) {
standard_value_[i] = value[i];
}
}
QVector<NodeKeyframe *>
NodeInputImmediate::get_keyframe_at_time(const rational &time) const
{
QVector<NodeKeyframe *> keys;
for (int i = 0; i < keyframe_tracks_.size(); i++) {
NodeKeyframe *key_at_time = get_keyframe_at_time_on_track(time, i);
if (key_at_time) {
keys.append(key_at_time);
}
}
return keys;
}
NodeKeyframe *
NodeInputImmediate::get_keyframe_at_time_on_track(const rational &time,
int track) const
{
if (!is_using_standard_value(track)) {
foreach (NodeKeyframe *key, keyframe_tracks_.at(track)) {
if (key->time() == time) {
return key;
}
}
}
return nullptr;
}
NodeKeyframe *
NodeInputImmediate::get_closest_keyframe_to_time_on_track(const rational &time,
int track) const
{
if (is_using_standard_value(track)) {
return nullptr;
}
const NodeKeyframeTrack &key_track = keyframe_tracks_.at(track);
if (time <= key_track.first()->time()) {
return key_track.first();
}
if (time >= key_track.last()->time()) {
return key_track.last();
}
for (int i = 1; i < key_track.size(); i++) {
NodeKeyframe *prev_key = key_track.at(i - 1);
NodeKeyframe *next_key = key_track.at(i);
if (prev_key->time() <= time && next_key->time() >= time) {
// Return whichever is closer
rational prev_diff = time - prev_key->time();
rational next_diff = next_key->time() - time;
if (next_diff < prev_diff) {
return next_key;
} else {
return prev_key;
}
}
}
return nullptr;
}
NodeKeyframe *
NodeInputImmediate::get_closest_keyframe_before_time(const rational &time) const
{
NodeKeyframe *key = nullptr;
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
foreach (NodeKeyframe *k, track) {
if (k->time() >= time) {
break;
} else if (!key || k->time() > key->time()) {
key = k;
}
}
}
return key;
}
NodeKeyframe *
NodeInputImmediate::get_closest_keyframe_after_time(const rational &time) const
{
NodeKeyframe *key = nullptr;
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
for (int i = track.size() - 1; i >= 0; i--) {
NodeKeyframe *k = track.at(i);
if (k->time() <= time) {
break;
} else if (!key || k->time() < key->time()) {
key = k;
}
}
}
return key;
}
NodeKeyframe::Type
NodeInputImmediate::get_best_keyframe_type_for_time(const rational &time,
int track) const
{
NodeKeyframe *closest_key =
get_closest_keyframe_to_time_on_track(time, track);
if (closest_key) {
return closest_key->type();
}
return NodeKeyframe::kDefaultType;
}
bool NodeInputImmediate::has_keyframe_at_time(const rational &time) const
{
if (!is_keyframing()) {
return false;
}
// Loop through keyframes to see if any match
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
foreach (NodeKeyframe *key, track) {
if (key->time() == time) {
return true;
}
}
}
// None match
return false;
}
void NodeInputImmediate::set_data_type(NodeValue::Type type)
{
int track_size = NodeValue::get_number_of_keyframe_tracks(type);
keyframe_tracks_.resize(track_size);
standard_value_.resize(track_size);
set_split_standard_value(default_value_);
}
NodeKeyframe *NodeInputImmediate::get_earliest_keyframe() const
{
NodeKeyframe *earliest = nullptr;
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
if (!track.isEmpty()) {
NodeKeyframe *earliest_in_track = track.first();
if (!earliest || earliest_in_track->time() < earliest->time()) {
earliest = earliest_in_track;
}
}
}
return earliest;
}
NodeKeyframe *NodeInputImmediate::get_latest_keyframe() const
{
NodeKeyframe *latest = nullptr;
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
if (!track.isEmpty()) {
NodeKeyframe *latest_in_track = track.last();
if (!latest || latest_in_track->time() > latest->time()) {
latest = latest_in_track;
}
}
}
return latest;
}
void NodeInputImmediate::insert_keyframe(NodeKeyframe *key)
{
NodeKeyframeTrack &key_track = keyframe_tracks_[key->track()];
int insert_index = key_track.size();
for (int i = 0; i < key_track.size(); i++) {
NodeKeyframe *compare = key_track.at(i);
// Ensure we aren't trying to insert two keyframes at the same time
Q_ASSERT(compare->time() != key->time());
if (compare->time() > key->time()) {
insert_index = i;
break;
}
}
key_track.insert(insert_index, key);
NodeKeyframe *previous = insert_index > 0 ? key_track.at(insert_index - 1) :
nullptr;
NodeKeyframe *next = insert_index < key_track.size() - 1 ?
key_track.at(insert_index + 1) :
nullptr;
key->set_previous(previous);
key->set_next(next);
if (previous) {
previous->set_next(key);
}
if (next) {
next->set_previous(key);
}
}
void NodeInputImmediate::remove_keyframe(NodeKeyframe *key)
{
if (key->previous()) {
key->previous()->set_next(key->next());
}
if (key->next()) {
key->next()->set_previous(key->previous());
}
key->set_previous(nullptr);
key->set_next(nullptr);
keyframe_tracks_[key->track()].removeOne(key);
}
void NodeInputImmediate::delete_all_keyframes(QObject *parent)
{
for (NodeKeyframeTrack &track : keyframe_tracks_) {
// Iterate over a copy of the track, since the keyframes may be removed
// from it as we go
const NodeKeyframeTrack copy = track;
for (NodeKeyframe *key : copy) {
if (!dynamic_cast<Node *>(key->QObject::parent())) {
// Keyframe isn't parented to a node, so reparenting/deleting it
// won't remove it from the track automatically
remove_keyframe(key);
}
if (parent) {
key->setParent(parent);
} else {
delete key;
}
}
}
}
}