implemented core audio recording support

This commit is contained in:
itsmattkc
2022-05-02 14:00:03 -07:00
parent a34fb2145d
commit 9101f5dc49
33 changed files with 610 additions and 111 deletions
@@ -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;
+4 -4
View File
@@ -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);
}
+2 -2
View File
@@ -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,
+78 -1
View File
@@ -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;
}
}
}
+12
View File
@@ -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();
+74 -11
View File
@@ -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;
+14
View File
@@ -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();