Merge branch 'master' into nodeview-redux
This commit is contained in:
@@ -162,6 +162,28 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r
|
||||
length_ = qMax(length_, dest + length);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
Sample& our_arr = it->second;
|
||||
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// Get our destination sample
|
||||
int our_start_index = time_to_samples(start, rate_dbl);
|
||||
int our_length_index = time_to_samples(length, rate_dbl);
|
||||
int our_end_index = our_start_index + our_length_index;
|
||||
|
||||
if (our_arr.size() < our_end_index) {
|
||||
our_arr.resize(our_end_index);
|
||||
}
|
||||
|
||||
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index, 0, our_length_index);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
{
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
@@ -184,9 +206,7 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
// Shifting backwards <-
|
||||
int copy_sz = data.size() - from_index;
|
||||
|
||||
for (int i=0; i<copy_sz; i++) {
|
||||
data.replace(to_index + i, data.at(from_index + i));
|
||||
}
|
||||
memcpy(&data.data()[to_index], &data.data()[from_index], copy_sz * sizeof(SamplePerChannel));
|
||||
|
||||
data.resize(data.size() - (from_index - to_index));
|
||||
} else {
|
||||
@@ -199,9 +219,10 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
|
||||
int copy_sz = old_sz - from_index;
|
||||
|
||||
for (int i=0; i<copy_sz; i++) {
|
||||
data.replace(data.size() - i - 1, data.at(old_sz - i - 1));
|
||||
}
|
||||
// Copy to a temporary buffer first to prevent overwriting bytes we need to copy
|
||||
QByteArray temp(copy_sz * sizeof(SamplePerChannel), Qt::Uninitialized);
|
||||
memcpy(temp.data(), &data.data()[from_index], temp.size());
|
||||
memcpy(&data.data()[to_index], temp.data(), temp.size());
|
||||
|
||||
memset(reinterpret_cast<char*>(&data[from_index]), 0, distance * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ public:
|
||||
*/
|
||||
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
|
||||
|
||||
void OverwriteSilence(const rational &start, const rational &length);
|
||||
|
||||
void Shift(const rational& from, const rational& to);
|
||||
|
||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||
|
||||
@@ -148,10 +148,14 @@ bool FFmpegDecoder::OpenInternal()
|
||||
|
||||
FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ¶ms)
|
||||
{
|
||||
if (!InitScaler(params)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AVStream* s = instance_.avstream();
|
||||
|
||||
// Retrieve frame
|
||||
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, params);
|
||||
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode);
|
||||
|
||||
// We found the frame, we'll return a copy
|
||||
if (return_frame) {
|
||||
@@ -161,7 +165,7 @@ FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const Re
|
||||
native_pix_fmt_,
|
||||
native_channel_count_,
|
||||
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect,
|
||||
VideoParams::kInterlaceNone, // May be incorrect
|
||||
VideoParams::kInterlaceNone,
|
||||
filter_params_.divider));
|
||||
copy->set_timestamp(timecode);
|
||||
copy->allocate();
|
||||
@@ -182,13 +186,9 @@ void FFmpegDecoder::CloseInternal()
|
||||
instance_.Close();
|
||||
}
|
||||
|
||||
int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame, const RetrieveVideoParams& params)
|
||||
int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame)
|
||||
{
|
||||
// Ensure scaler is correct for these parameters
|
||||
if (!InitScaler(params)) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
int ret;
|
||||
|
||||
AVFrame* working_frame = av_frame_alloc();
|
||||
@@ -642,15 +642,18 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f)
|
||||
|
||||
void FFmpegDecoder::ClearFrameCache()
|
||||
{
|
||||
cached_frames_.clear();
|
||||
cache_at_eof_ = false;
|
||||
cache_at_zero_ = false;
|
||||
if (!cached_frames_.isEmpty()) {
|
||||
cached_frames_.clear();
|
||||
cache_at_eof_ = false;
|
||||
cache_at_zero_ = false;
|
||||
|
||||
// Filter graph may rely on "continuous" video frames, so we free the scaler here
|
||||
FreeScaler();
|
||||
// Filter graph may rely on "continuous" video frames, so we free the scaler here
|
||||
FreeScaler();
|
||||
InitScaler(filter_params_);
|
||||
}
|
||||
}
|
||||
|
||||
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, const RetrieveVideoParams ¶ms)
|
||||
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time)
|
||||
{
|
||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
||||
|
||||
@@ -658,7 +661,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
|
||||
int64_t seek_ts = target_ts;
|
||||
bool still_seeking = false;
|
||||
|
||||
if (params.src_interlacing != VideoParams::kInterlaceNone) {
|
||||
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) {
|
||||
// If we are de-interlacing, the timebase is doubled because we get one frame per field, so we
|
||||
// double the target timestamp too
|
||||
target_ts *= 2;
|
||||
@@ -696,7 +699,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
|
||||
|
||||
// Pull from the decoder
|
||||
av_frame_unref(working_frame);
|
||||
ret = GetFilteredFrame(pkt, working_frame, params);
|
||||
ret = GetFilteredFrame(pkt, working_frame);
|
||||
|
||||
// Handle any errors that aren't EOF (EOF is handled later on)
|
||||
if (ret < 0 && ret != AVERROR_EOF) {
|
||||
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
int GetFilteredFrame(AVPacket *packet, AVFrame *frame, const RetrieveVideoParams ¶ms);
|
||||
int GetFilteredFrame(AVPacket *packet, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief Handle an FFmpeg error code
|
||||
@@ -138,7 +138,7 @@ private:
|
||||
|
||||
void ClearFrameCache();
|
||||
|
||||
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, const RetrieveVideoParams ¶ms);
|
||||
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time);
|
||||
|
||||
void RemoveFirstFrame();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace olive {
|
||||
|
||||
class ExportSubtitlesTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportSubtitlesTab(QWidget *parent = nullptr);
|
||||
|
||||
|
||||
@@ -47,14 +47,14 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const Q
|
||||
const QString& mat_in = (type_a == NodeValue::kTexture) ? param_b_in : param_a_in;
|
||||
|
||||
// No-op frag shader (can we return QString() instead?)
|
||||
operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in);
|
||||
operation = QStringLiteral("texture2D(%1, ove_texcoord)").arg(tex_in);
|
||||
|
||||
vert = QStringLiteral("uniform mat4 %1;\n"
|
||||
"\n"
|
||||
"in vec4 a_position;\n"
|
||||
"in vec2 a_texcoord;\n"
|
||||
"attribute vec4 a_position;\n"
|
||||
"attribute vec2 a_texcoord;\n"
|
||||
"\n"
|
||||
"out vec2 ove_texcoord;\n"
|
||||
"varying vec2 ove_texcoord;\n"
|
||||
"\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = %1 * a_position;\n"
|
||||
@@ -96,12 +96,10 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const Q
|
||||
frag = QStringLiteral("uniform %1 %3;\n"
|
||||
"uniform %2 %4;\n"
|
||||
"\n"
|
||||
"in vec2 ove_texcoord;\n"
|
||||
"\n"
|
||||
"out vec4 fragColor;\n"
|
||||
"varying vec2 ove_texcoord;\n"
|
||||
"\n"
|
||||
"void main(void) {\n"
|
||||
" fragColor = %5;\n"
|
||||
" gl_FragColor = %5;\n"
|
||||
"}\n").arg(GetShaderUniformType(type_a),
|
||||
GetShaderUniformType(type_b),
|
||||
param_a_in,
|
||||
@@ -128,7 +126,7 @@ QString MathNodeBase::GetShaderUniformType(const olive::NodeValue::Type &type)
|
||||
QString MathNodeBase::GetShaderVariableCall(const QString &input_id, const NodeValue::Type &type, const QString& coord_op)
|
||||
{
|
||||
if (type == NodeValue::kTexture) {
|
||||
return QStringLiteral("texture(%1, ove_texcoord%2)").arg(input_id, coord_op);
|
||||
return QStringLiteral("texture2D(%1, ove_texcoord%2)").arg(input_id, coord_op);
|
||||
}
|
||||
|
||||
return input_id;
|
||||
|
||||
@@ -48,6 +48,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms)
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
visual_.set_channel_count(params_.channel_count());
|
||||
|
||||
// Restart empty file so there's always "something" to play
|
||||
ClearPlaylist();
|
||||
@@ -55,7 +56,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms)
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const qint64 &job_time)
|
||||
void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64 &job_time)
|
||||
{
|
||||
QList<TimeRange> valid_ranges = GetValidRanges(range, job_time);
|
||||
if (valid_ranges.isEmpty()) {
|
||||
@@ -83,6 +84,7 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
rational this_segment_in = 0;
|
||||
|
||||
// Write PCM to playlist
|
||||
for (auto it=playlist_.begin(); it!=playlist_.end(); it++) {
|
||||
rational this_segment_out = this_segment_in + params_.bytes_to_time((*it).size());
|
||||
|
||||
@@ -138,6 +140,13 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
|
||||
// Each segment is contiguous, so this out will be the next segment's in
|
||||
this_segment_in = this_segment_out;
|
||||
}
|
||||
|
||||
// Write visual
|
||||
if (waveform) {
|
||||
visual_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
||||
} else {
|
||||
visual_.OverwriteSilence(r.in(), r.length());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const TimeRange& v, ranges_we_validated) {
|
||||
@@ -149,7 +158,7 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range, qint64 job_time)
|
||||
{
|
||||
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
|
||||
// it an empty sample buffer
|
||||
WritePCM(range, nullptr, job_time);
|
||||
WritePCM(range, nullptr, nullptr, job_time);
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifndef AUDIOPLAYBACKCACHE_H
|
||||
#define AUDIOPLAYBACKCACHE_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "common/timerange.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "render/playbackcache.h"
|
||||
@@ -65,7 +66,7 @@ public:
|
||||
|
||||
void SetParameters(const AudioParams& params);
|
||||
|
||||
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const qint64& job_time);
|
||||
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64& job_time);
|
||||
|
||||
void WriteSilence(const TimeRange &range, qint64 job_time);
|
||||
|
||||
@@ -181,6 +182,11 @@ public:
|
||||
*/
|
||||
PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const;
|
||||
|
||||
const AudioVisualWaveform &visual() const
|
||||
{
|
||||
return visual_;
|
||||
}
|
||||
|
||||
signals:
|
||||
void ParametersChanged();
|
||||
|
||||
@@ -212,6 +218,8 @@ private:
|
||||
|
||||
AudioParams params_;
|
||||
|
||||
AudioVisualWaveform visual_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -39,9 +39,12 @@ namespace olive {
|
||||
|
||||
QMutex FrameHashCache::currently_saving_frames_mutex_;
|
||||
QMap<QByteArray, FramePtr> FrameHashCache::currently_saving_frames_;
|
||||
const QString FrameHashCache::kCacheFormatExtension = QStringLiteral(".exr");
|
||||
|
||||
#define super PlaybackCache
|
||||
|
||||
FrameHashCache::FrameHashCache(QObject *parent) :
|
||||
PlaybackCache(parent)
|
||||
super(parent)
|
||||
{
|
||||
if (DiskManager::instance()) {
|
||||
connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &FrameHashCache::HashDeleted);
|
||||
@@ -419,11 +422,9 @@ QString FrameHashCache::CachePathName(const QByteArray& hash) const
|
||||
|
||||
QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArray &hash)
|
||||
{
|
||||
QString ext = GetFormatExtension();
|
||||
|
||||
QDir cache_dir(QDir(cache_path).filePath(QString(hash.left(1).toHex())));
|
||||
|
||||
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext);
|
||||
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), kCacheFormatExtension);
|
||||
|
||||
// Register that in some way this hash has been accessed
|
||||
QMetaObject::invokeMethod(DiskManager::instance(),
|
||||
|
||||
@@ -70,8 +70,6 @@ public:
|
||||
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
||||
static FramePtr LoadCacheFrame(const QString& fn);
|
||||
|
||||
static QString GetFormatExtension();
|
||||
|
||||
static QVector<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
|
||||
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
|
||||
QVector<rational> GetInvalidatedFrames();
|
||||
@@ -94,6 +92,7 @@ private:
|
||||
|
||||
static QMutex currently_saving_frames_mutex_;
|
||||
static QMap<QByteArray, FramePtr> currently_saving_frames_;
|
||||
static const QString kCacheFormatExtension;
|
||||
|
||||
private slots:
|
||||
void HashDeleted(const QString &s, const QByteArray& hash);
|
||||
|
||||
@@ -171,8 +171,11 @@ void PreviewAutoCacher::AudioRendered()
|
||||
if (watcher->HasResult()) {
|
||||
const TimeRange &range = audio_tasks_.value(watcher);
|
||||
|
||||
AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value<AudioVisualWaveform>();
|
||||
|
||||
viewer_node_->audio_playback_cache()->WritePCM(range,
|
||||
watcher->Get().value<SampleBufferPtr>(),
|
||||
&waveform,
|
||||
watcher->GetTicket()->GetJobTime());
|
||||
|
||||
bool pcm_is_usable = true;
|
||||
@@ -506,7 +509,7 @@ void PreviewAutoCacher::TryRender()
|
||||
|
||||
if (!invalidated_audio_.isEmpty()) {
|
||||
foreach (const TimeRange& range, invalidated_audio_) {
|
||||
std::list<TimeRange> chunks = range.Split(2);
|
||||
std::list<TimeRange> chunks = range.Split(30);
|
||||
|
||||
foreach (const TimeRange& r, chunks) {
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
|
||||
@@ -173,7 +173,16 @@ void RenderProcessor::Run()
|
||||
table = GenerateTable(texture_output.node(), texture_output.output(), time);
|
||||
}
|
||||
|
||||
ticket_->Finish(table.Get(NodeValue::kSamples));
|
||||
QVariant sample_variant = table.Get(NodeValue::kSamples);
|
||||
SampleBufferPtr samples = sample_variant.value<SampleBufferPtr>();
|
||||
if (samples && ticket_->property("enablewaveforms").toBool()) {
|
||||
AudioVisualWaveform vis;
|
||||
vis.set_channel_count(samples->audio_params().channel_count());
|
||||
vis.OverwriteSamples(samples, samples->audio_params().sample_rate());
|
||||
ticket_->setProperty("waveform", QVariant::fromValue(vis));
|
||||
}
|
||||
|
||||
ticket_->Finish(sample_variant);
|
||||
break;
|
||||
}
|
||||
case RenderManager::kTypeVideoDownload:
|
||||
|
||||
+993
-209
File diff suppressed because it is too large
Load Diff
+3034
-2090
File diff suppressed because it is too large
Load Diff
+2483
-1771
File diff suppressed because it is too large
Load Diff
+1968
-933
File diff suppressed because it is too large
Load Diff
+7
-8
@@ -266,8 +266,7 @@ Length: %4</source>
|
||||
<message>
|
||||
<location filename="../tool/tool.h" line="108"/>
|
||||
<source>Bars</source>
|
||||
<translatorcomment>彩条</translatorcomment>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">彩条</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../tool/tool.h" line="110"/>
|
||||
@@ -282,8 +281,7 @@ Length: %4</source>
|
||||
<message>
|
||||
<location filename="../tool/tool.h" line="114"/>
|
||||
<source>Tone</source>
|
||||
<translatorcomment>音调</translatorcomment>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">音调</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../tool/tool.h" line="119"/>
|
||||
@@ -1425,7 +1423,8 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
||||
<message>
|
||||
<location filename="../dialog/export/exportvideotab.cpp" line="57"/>
|
||||
<source>Basic</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translatorcomment>Would "基本" / "基本的" be more appropriate?</translatorcomment>
|
||||
<translation type="unfinished">基本设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dialog/export/exportvideotab.cpp" line="61"/>
|
||||
@@ -2032,7 +2031,7 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
||||
<message>
|
||||
<location filename="../window/mainwindow/mainmenu.cpp" line="664"/>
|
||||
<source>Cache Entire Sequence</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">缓存整个序列</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window/mainwindow/mainmenu.cpp" line="665"/>
|
||||
@@ -2112,7 +2111,7 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
||||
<message>
|
||||
<location filename="../window/mainwindow/mainmenu.cpp" line="685"/>
|
||||
<source>Enable Snapping</source>
|
||||
<translation type="unfinished">开启边缘吸合/自动对齐</translation>
|
||||
<translation type="unfinished">启用吸附</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window/mainwindow/mainmenu.cpp" line="686"/>
|
||||
@@ -4369,7 +4368,7 @@ What would you like to do with these clips?</source>
|
||||
<message>
|
||||
<location filename="../widget/toolbar/toolbar.cpp" line="108"/>
|
||||
<source>Toggle Snapping</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">切换吸附</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -1370,6 +1370,8 @@ public:
|
||||
|
||||
Track* track = timeline_->GetTrackAt(track_index_);
|
||||
|
||||
track->BeginOperation();
|
||||
|
||||
bool append = (in_ >= track->track_length());
|
||||
|
||||
// Check if the placement location is past the end of the timeline
|
||||
@@ -1409,6 +1411,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
track->EndOperation();
|
||||
|
||||
if (ripple_remove_command_) {
|
||||
track->Node::InvalidateCache(TimeRange(insert_->in(), insert_->out()), Track::kBlockInput);
|
||||
}
|
||||
|
||||
for (int i=0; i<position_commands_.size(); i++) {
|
||||
position_commands_.at(i)->redo();
|
||||
}
|
||||
@@ -1422,7 +1430,10 @@ public:
|
||||
|
||||
Track* t = timeline_->GetTrackAt(track_index_);
|
||||
|
||||
TimeRange insert_range(insert_->in(), insert_->out());
|
||||
|
||||
// Firstly, remove our insert
|
||||
t->BeginOperation();
|
||||
t->RippleRemoveBlock(insert_);
|
||||
|
||||
if (ripple_remove_command_) {
|
||||
@@ -1432,6 +1443,11 @@ public:
|
||||
t->RippleRemoveBlock(gap_);
|
||||
gap_->setParent(&memory_manager_);
|
||||
}
|
||||
t->EndOperation();
|
||||
|
||||
if (ripple_remove_command_) {
|
||||
t->Node::InvalidateCache(insert_range, Track::kBlockInput);
|
||||
}
|
||||
|
||||
// Remove tracks if we added them
|
||||
for (int i=add_track_commands_.size()-1; i>=0; i--) {
|
||||
|
||||
@@ -87,7 +87,11 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event)
|
||||
PrepGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
|
||||
drag_start_.GetTrack().index());
|
||||
|
||||
event->accept();
|
||||
if (parent()->HasGhosts() || !parent()->GetConnectedNode()) {
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
} else {
|
||||
// FIXME: Implement dropping from file
|
||||
event->ignore();
|
||||
|
||||
@@ -60,8 +60,7 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
|
||||
pool_.clear();
|
||||
pool_.waitForDone();
|
||||
|
||||
disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange);
|
||||
//disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange);
|
||||
disconnect(playback_, &AudioPlaybackCache::Validated, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
||||
|
||||
SetTimebase(0);
|
||||
}
|
||||
@@ -69,14 +68,9 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
|
||||
playback_ = playback;
|
||||
|
||||
if (playback_) {
|
||||
connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange);
|
||||
//connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange);
|
||||
connect(playback_, &AudioPlaybackCache::Validated, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
||||
|
||||
SetTimebase(playback_->GetParameters().sample_rate_as_time_base());
|
||||
|
||||
waveform_.set_channel_count(playback_->GetParameters().channel_count());
|
||||
|
||||
RenderRange(TimeRange(0, playback_->GetLength()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +95,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
|
||||
|
||||
// Draw waveform
|
||||
p.setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color
|
||||
AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), waveform_, SceneToTime(GetScroll()));
|
||||
AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), playback_->visual(), SceneToTime(GetScroll()));
|
||||
|
||||
// Draw playhead
|
||||
p.setPen(PLAYHEAD_COLOR);
|
||||
@@ -110,42 +104,4 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
|
||||
p.drawLine(playhead_x, 0, playhead_x, height());
|
||||
}
|
||||
|
||||
void AudioWaveformView::RenderRange(TimeRange range)
|
||||
{
|
||||
// Limit range to length
|
||||
range = TimeRange(qMax(rational(0), range.in()), qMin(playback_->GetLength(), range.out()));
|
||||
|
||||
// Floor to second increments
|
||||
int64_t start = qFloor(range.in().toDouble());
|
||||
int64_t end = qCeil(range.out().toDouble());
|
||||
|
||||
for (; start!=end; start++) {
|
||||
TimeRange this_range(start, start+1);
|
||||
|
||||
QFutureWatcher<AudioVisualWaveform>* watcher = new QFutureWatcher<AudioVisualWaveform>();
|
||||
connect(watcher, &QFutureWatcher<AudioVisualWaveform>::finished, this, &AudioWaveformView::BackgroundFinished);
|
||||
|
||||
jobs_.insert(this_range, watcher);
|
||||
|
||||
watcher->setFuture(QtConcurrent::run(&pool_, GenerateWaveform, playback_->CreatePlaybackDevice(), playback_->GetParameters(), this_range));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioWaveformView::BackgroundFinished()
|
||||
{
|
||||
QFutureWatcher<AudioVisualWaveform>* watcher = static_cast<QFutureWatcher<AudioVisualWaveform>*>(sender());
|
||||
|
||||
for (auto it=jobs_.begin(); it!=jobs_.end(); it++) {
|
||||
if (it.value() == watcher) {
|
||||
AudioVisualWaveform rendered = watcher->result();
|
||||
waveform_.OverwriteSums(rendered, it.key().in());
|
||||
jobs_.erase(it);
|
||||
update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QWidget>
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/audioplaybackcache.h"
|
||||
#include "widget/timeruler/seekablewidget.h"
|
||||
@@ -37,32 +36,16 @@ class AudioWaveformView : public SeekableWidget
|
||||
public:
|
||||
AudioWaveformView(QWidget* parent = nullptr);
|
||||
|
||||
//void SetData(const QString& file, const AudioRenderingParams& params);
|
||||
|
||||
void SetViewer(AudioPlaybackCache *playback);
|
||||
|
||||
const AudioVisualWaveform* waveform() const
|
||||
{
|
||||
return &waveform_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void RenderRange(TimeRange range);
|
||||
|
||||
QThreadPool pool_;
|
||||
|
||||
AudioPlaybackCache *playback_;
|
||||
|
||||
AudioVisualWaveform waveform_;
|
||||
|
||||
QHash<TimeRange, QFutureWatcher<AudioVisualWaveform>*> jobs_;
|
||||
|
||||
private slots:
|
||||
void BackgroundFinished();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ void ViewerWidget::StartAudioOutput()
|
||||
AudioManager::instance()->StartOutput(audio_cache,
|
||||
audio_cache->GetParameters().time_to_bytes(GetTime()),
|
||||
playback_speed_);
|
||||
emit AudioManager::instance()->OutputWaveformStarted(waveform_view_->waveform(),
|
||||
emit AudioManager::instance()->OutputWaveformStarted(&audio_cache->visual(),
|
||||
GetTime(), playback_speed_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,8 +639,7 @@ void MainMenu::Retranslate()
|
||||
|
||||
// Edit menu
|
||||
edit_menu_->setTitle(tr("&Edit"));
|
||||
//edit_undo_item_->setText(tr("&Undo")); FIXME: Does Qt translate these automatically?
|
||||
//edit_redo_item_->setText(tr("Redo"));
|
||||
Core::instance()->undo_stack()->UpdateActions(); // Update undo and redo
|
||||
edit_delete2_item_->setText(tr("Delete (alt)"));
|
||||
edit_insert_item_->setText(tr("Insert"));
|
||||
edit_overwrite_item_->setText(tr("Overwrite"));
|
||||
|
||||
Reference in New Issue
Block a user