implemented core audio recording support
This commit is contained in:
@@ -156,6 +156,10 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
|
||||
SetAudioVideoDragButtonsVisible(false);
|
||||
|
||||
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged);
|
||||
|
||||
play_blink_timer_ = new QTimer(this);
|
||||
play_blink_timer_->setInterval(500);
|
||||
connect(play_blink_timer_, &QTimer::timeout, this, &PlaybackControls::PlayBlink);
|
||||
}
|
||||
|
||||
void PlaybackControls::SetTimecodeEnabled(bool enabled)
|
||||
@@ -231,10 +235,20 @@ void PlaybackControls::UpdateIcons()
|
||||
audio_drag_btn_->setIcon(icon::Audio);
|
||||
}
|
||||
|
||||
void PlaybackControls::SetButtonRecordingState(QPushButton *btn, bool on)
|
||||
{
|
||||
btn->setStyleSheet(on ? QStringLiteral("background: red;") : QString());
|
||||
}
|
||||
|
||||
void PlaybackControls::TimecodeChanged()
|
||||
{
|
||||
// Update end time
|
||||
SetEndTime(end_time_);
|
||||
}
|
||||
|
||||
void PlaybackControls::PlayBlink()
|
||||
{
|
||||
SetButtonRecordingState(play_btn_, play_btn_->styleSheet().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,23 @@ public slots:
|
||||
|
||||
void ShowPlayButton();
|
||||
|
||||
void StartPlayBlink()
|
||||
{
|
||||
play_blink_timer_->start();
|
||||
SetButtonRecordingState(play_btn_, true);
|
||||
}
|
||||
|
||||
void StopPlayBlink()
|
||||
{
|
||||
play_blink_timer_->stop();
|
||||
SetButtonRecordingState(play_btn_, false);
|
||||
}
|
||||
|
||||
void SetPauseButtonRecordingState(bool on)
|
||||
{
|
||||
SetButtonRecordingState(pause_btn_, on);
|
||||
}
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when "Go to Start" is clicked
|
||||
@@ -108,6 +125,8 @@ protected:
|
||||
private:
|
||||
void UpdateIcons();
|
||||
|
||||
static void SetButtonRecordingState(QPushButton *btn, bool on);
|
||||
|
||||
QWidget* lower_left_container_;
|
||||
QWidget* lower_right_container_;
|
||||
|
||||
@@ -129,9 +148,13 @@ private:
|
||||
|
||||
QStackedWidget* playpause_stack_;
|
||||
|
||||
QTimer *play_blink_timer_;
|
||||
|
||||
private slots:
|
||||
void TimecodeChanged();
|
||||
|
||||
void PlayBlink();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "dialog/speedduration/speeddurationdialog.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "task/project/import/import.h"
|
||||
#include "tool/add.h"
|
||||
#include "tool/beam.h"
|
||||
#include "tool/edit.h"
|
||||
@@ -788,6 +789,35 @@ void TimelineWidget::ShowSpeedDurationDialogForSelectedClips()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
ProjectImportTask task(GetConnectedNode()->project()->root(), {filename});
|
||||
task.Start();
|
||||
|
||||
MultiUndoCommand *import_command = task.GetCommand();
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||
|
||||
if (task.GetImportedFootage().empty()) {
|
||||
qCritical() << "Failed to import recorded audio file" << filename;
|
||||
} else {
|
||||
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
tview->view()->EnableRecordingOverlay(coord);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::DisableRecordingOverlay()
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
tview->view()->DisableRecordingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command)
|
||||
{
|
||||
for (int i=0;i<Track::kCount;i++) {
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
|
||||
void ShowSpeedDurationDialogForSelectedClips();
|
||||
|
||||
void RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
void EnableRecordingOverlay(const TimelineCoordinate &coord);
|
||||
|
||||
void DisableRecordingOverlay();
|
||||
|
||||
/**
|
||||
* @brief Timelines should always be connected to sequences
|
||||
*/
|
||||
@@ -261,6 +267,8 @@ public:
|
||||
signals:
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert)
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
|
||||
{
|
||||
DraggedFootageData refs;
|
||||
|
||||
@@ -181,10 +181,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
|
||||
refs.append({f, f->GetEnabledStreamsAsReferences()});
|
||||
}
|
||||
|
||||
PlaceAt(refs, start, insert);
|
||||
PlaceAt(refs, start, insert, track_offset);
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert)
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
|
||||
{
|
||||
dragged_footage_ = footage;
|
||||
|
||||
@@ -192,7 +192,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
|
||||
return;
|
||||
}
|
||||
|
||||
PrepGhosts(start, 0);
|
||||
PrepGhosts(start, track_offset);
|
||||
DropGhosts(insert);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ public:
|
||||
|
||||
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
|
||||
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert);
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
|
||||
enum DropWithoutSequenceBehavior {
|
||||
kDWSAsk,
|
||||
|
||||
@@ -1,11 +1,88 @@
|
||||
#include "record.h"
|
||||
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
RecordTool::RecordTool(TimelineWidget *parent) :
|
||||
BeamTool(parent)
|
||||
BeamTool(parent),
|
||||
ghost_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RecordTool::MousePress(TimelineViewMouseEvent *event)
|
||||
{
|
||||
const Track::Reference& track = event->GetTrack();
|
||||
|
||||
// Check if track is locked
|
||||
Track* t = parent()->GetTrackFromReference(track);
|
||||
if (t && t->IsLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t->type() != Track::kAudio) {
|
||||
// We only support audio tracks here
|
||||
return;
|
||||
}
|
||||
|
||||
drag_start_point_ = ValidatedCoordinate(event->GetCoordinates(true)).GetFrame();
|
||||
|
||||
ghost_ = new TimelineViewGhostItem();
|
||||
ghost_->SetIn(drag_start_point_);
|
||||
ghost_->SetOut(drag_start_point_);
|
||||
ghost_->SetTrack(track);
|
||||
parent()->AddGhost(ghost_);
|
||||
|
||||
snap_points_.push_back(drag_start_point_);
|
||||
}
|
||||
|
||||
void RecordTool::MouseMove(TimelineViewMouseEvent *event)
|
||||
{
|
||||
if (!ghost_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate movement
|
||||
rational movement = event->GetFrame() - drag_start_point_;
|
||||
|
||||
// Validation: Ensure in point never goes below 0
|
||||
if (movement < -ghost_->GetIn()) {
|
||||
movement = -ghost_->GetIn();
|
||||
}
|
||||
|
||||
// Snap movement
|
||||
bool snapped;
|
||||
|
||||
if (Core::instance()->snapping()) {
|
||||
snapped = parent()->SnapPoint(snap_points_, &movement);
|
||||
} else {
|
||||
snapped = false;
|
||||
}
|
||||
|
||||
// Make adjustment
|
||||
if (!movement) {
|
||||
ghost_->SetInAdjustment(0);
|
||||
ghost_->SetOutAdjustment(0);
|
||||
} else if (movement > 0) {
|
||||
ghost_->SetInAdjustment(0);
|
||||
ghost_->SetOutAdjustment(movement);
|
||||
} else if (movement < 0) {
|
||||
ghost_->SetInAdjustment(movement);
|
||||
ghost_->SetOutAdjustment(0);
|
||||
}
|
||||
|
||||
Q_UNUSED(snapped)
|
||||
}
|
||||
|
||||
void RecordTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
{
|
||||
if (ghost_) {
|
||||
emit parent()->RequestCaptureStart(TimeRange(ghost_->GetAdjustedIn(), ghost_->GetAdjustedOut()), ghost_->GetTrack());
|
||||
parent()->ClearGhosts();
|
||||
snap_points_.clear();
|
||||
ghost_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,18 @@ class RecordTool : public BeamTool
|
||||
{
|
||||
public:
|
||||
RecordTool(TimelineWidget* parent);
|
||||
|
||||
virtual void MousePress(TimelineViewMouseEvent *event) override;
|
||||
virtual void MouseMove(TimelineViewMouseEvent *event) override;
|
||||
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
|
||||
|
||||
protected:
|
||||
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
|
||||
|
||||
TimelineViewGhostItem* ghost_;
|
||||
|
||||
rational drag_start_point_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -348,6 +348,16 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
|
||||
track_y + GetTrackHeight(track_index));
|
||||
}
|
||||
|
||||
// Draw recording overlay
|
||||
if (recording_overlay_ && recording_coord_.GetTrack().type() == connected_track_list_->type()) {
|
||||
painter->setPen(QPen(Qt::red, 2));
|
||||
painter->setBrush(QColor(255, 128, 128));
|
||||
|
||||
int x = TimeToScene(recording_coord_.GetFrame());
|
||||
painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()),
|
||||
TimeToScene(GetTime()) - x, GetTrackHeight(recording_coord_.GetTrack().index()));
|
||||
}
|
||||
|
||||
// Draw standard TimelineViewBase things (such as playhead)
|
||||
super::drawForeground(painter, rect);
|
||||
}
|
||||
@@ -364,6 +374,7 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
|
||||
case Tool::kAdd:
|
||||
case Tool::kTransition:
|
||||
case Tool::kZoom:
|
||||
case Tool::kRecord :
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Tool::kTrackSelect:
|
||||
@@ -745,6 +756,19 @@ void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||
{
|
||||
recording_overlay_ = true;
|
||||
recording_coord_ = coord;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::DisableRecordingOverlay()
|
||||
{
|
||||
recording_overlay_ = false;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
int TimelineView::SceneToTrack(double y)
|
||||
{
|
||||
int track = -1;
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
|
||||
void SetBeamCursor(const TimelineCoordinate& coord);
|
||||
void SetTransitionOverlay(ClipBlock *out, ClipBlock *in);
|
||||
void EnableRecordingOverlay(const TimelineCoordinate &coord);
|
||||
void DisableRecordingOverlay();
|
||||
|
||||
void SetSelectionList(QHash<Track::Reference, TimeRangeList>* s)
|
||||
{
|
||||
@@ -157,6 +159,9 @@ private:
|
||||
|
||||
QMap<TimelineMarker*, QRectF> clip_marker_rects_;
|
||||
|
||||
bool recording_overlay_;
|
||||
TimelineCoordinate recording_coord_;
|
||||
|
||||
private slots:
|
||||
void TrackListChanged();
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
color_menu_enabled_(true),
|
||||
time_changed_from_timer_(false),
|
||||
prequeuing_video_(false),
|
||||
prequeuing_audio_(0)
|
||||
prequeuing_audio_(0),
|
||||
record_armed_(false),
|
||||
recording_(false)
|
||||
{
|
||||
// Set up main layout
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -158,6 +160,10 @@ void ViewerWidget::TimeChangedEvent(const rational &time)
|
||||
PauseInternal();
|
||||
}
|
||||
|
||||
if (record_armed_) {
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
controls_->SetTime(time);
|
||||
waveform_view_->SetTime(time);
|
||||
|
||||
@@ -377,6 +383,17 @@ void ViewerWidget::SetGizmos(Node *node)
|
||||
display_widget_->SetGizmos(node);
|
||||
}
|
||||
|
||||
void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
SetTimeAndSignal(time.in());
|
||||
ArmForRecording();
|
||||
|
||||
recording_filename_ = QStringLiteral("/home/matt/Desktop/ass.mp3");
|
||||
recording_callback_ = source;
|
||||
recording_range_ = time;
|
||||
recording_track_ = track;
|
||||
}
|
||||
|
||||
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QByteArray& hash, const rational& time)
|
||||
{
|
||||
FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, hash);
|
||||
@@ -428,6 +445,18 @@ void ViewerWidget::DecrementPrequeuedAudio()
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::ArmForRecording()
|
||||
{
|
||||
controls_->StartPlayBlink();
|
||||
record_armed_ = true;
|
||||
}
|
||||
|
||||
void ViewerWidget::DisarmRecording()
|
||||
{
|
||||
controls_->StopPlayBlink();
|
||||
record_armed_ = false;
|
||||
}
|
||||
|
||||
void ViewerWidget::QueueNextAudioBuffer()
|
||||
{
|
||||
rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_);
|
||||
@@ -595,13 +624,20 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
viewer->auto_cacher_.SetAudioPaused(true);
|
||||
}
|
||||
|
||||
// Disarm recording if armed
|
||||
if (record_armed_) {
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
// If the playhead is beyond the end, restart at 0
|
||||
rational last_frame = GetConnectedNode()->GetLength() - timebase();
|
||||
if (!in_to_out_only && GetTime() >= last_frame) {
|
||||
if (speed > 0) {
|
||||
SetTimeAndSignal(0);
|
||||
} else {
|
||||
SetTimeAndSignal(last_frame);
|
||||
if (!recording_) {
|
||||
rational last_frame = GetConnectedNode()->GetLength() - timebase();
|
||||
if (!in_to_out_only && GetTime() >= last_frame) {
|
||||
if (speed > 0) {
|
||||
SetTimeAndSignal(0);
|
||||
} else {
|
||||
SetTimeAndSignal(last_frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,6 +693,15 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
|
||||
void ViewerWidget::PauseInternal()
|
||||
{
|
||||
if (recording_) {
|
||||
AudioManager::instance()->StopRecording();
|
||||
recording_ = false;
|
||||
controls_->SetPauseButtonRecordingState(false);
|
||||
|
||||
recording_callback_->DisableRecordingOverlay();
|
||||
recording_callback_->RecordingCallback(recording_filename_, recording_range_, recording_track_);
|
||||
}
|
||||
|
||||
if (IsPlaying()) {
|
||||
playback_speed_ = 0;
|
||||
controls_->ShowPlayButton();
|
||||
@@ -1121,6 +1166,17 @@ void ViewerWidget::Play(bool in_to_out_only)
|
||||
} else {
|
||||
in_to_out_only = false;
|
||||
}
|
||||
} else if (record_armed_) {
|
||||
if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) {
|
||||
recording_ = true;
|
||||
controls_->SetPauseButtonRecordingState(true);
|
||||
recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_));
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording"));
|
||||
return;
|
||||
}
|
||||
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
PlayInternal(1, in_to_out_only);
|
||||
@@ -1205,7 +1261,13 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
|
||||
rational min_time, max_time;
|
||||
|
||||
if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
|
||||
if (recording_ && recording_range_.out() != recording_range_.in()) {
|
||||
|
||||
// Limit recording range if applicable
|
||||
min_time = recording_range_.in();
|
||||
max_time = recording_range_.out();
|
||||
|
||||
} else if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
|
||||
|
||||
// If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea
|
||||
min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in();
|
||||
@@ -1229,8 +1291,9 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
bool end_of_line = false;
|
||||
bool play_after_pause = false;
|
||||
|
||||
if ((playback_speed_ < 0 && current_time <= min_time)
|
||||
|| (playback_speed_ > 0 && current_time >= max_time)) {
|
||||
if ((!recording_ || recording_range_.out() != recording_range_.in())
|
||||
&& ((playback_speed_ < 0 && current_time <= min_time)
|
||||
|| (playback_speed_ > 0 && current_time >= max_time))) {
|
||||
|
||||
// Determine which timestamp we tripped
|
||||
rational tripped_time;
|
||||
@@ -1245,7 +1308,7 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
// or restart playback
|
||||
end_of_line = true;
|
||||
|
||||
if (Config::Current()[QStringLiteral("Loop")].toBool()) {
|
||||
if (Config::Current()[QStringLiteral("Loop")].toBool() && !recording_) {
|
||||
|
||||
// If we're looping, jump to the other side of the workarea and continue
|
||||
time_to_set = (tripped_time == min_time) ? max_time : min_time;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "viewerwindow.h"
|
||||
#include "widget/playbackcontrols/playbackcontrols.h"
|
||||
#include "widget/timebased/timebasedwidget.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -87,6 +88,8 @@ public:
|
||||
|
||||
void SetGizmos(Node* node);
|
||||
|
||||
void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
public slots:
|
||||
void Play(bool in_to_out_only);
|
||||
|
||||
@@ -205,6 +208,10 @@ private:
|
||||
|
||||
void DecrementPrequeuedAudio();
|
||||
|
||||
void ArmForRecording();
|
||||
|
||||
void DisarmRecording();
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
ViewerSizer* sizer_;
|
||||
@@ -255,6 +262,13 @@ private:
|
||||
|
||||
static QVector<ViewerWidget*> instances_;
|
||||
|
||||
bool record_armed_;
|
||||
bool recording_;
|
||||
TimelineWidget *recording_callback_;
|
||||
TimeRange recording_range_;
|
||||
Track::Reference recording_track_;
|
||||
QString recording_filename_;
|
||||
|
||||
private slots:
|
||||
void PlaybackTimerUpdate();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user