From 5d52ea28eea891d2cd5b9485a1348ec7302df157 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 13 Feb 2019 23:41:50 +0100 Subject: [PATCH] Add support for aborting playing a clip early. --- futatabi/behringer_cmd_pl1.midimapping | 4 ++++ futatabi/futatabi_midi_mapping.proto | 4 ++++ futatabi/mainwindow.cpp | 32 +++++++++++++++++++++++++- futatabi/mainwindow.h | 2 ++ futatabi/mainwindow.ui | 10 ++++++++ futatabi/midi_mapper.cpp | 5 ++++ futatabi/midi_mapper.h | 6 +++++ futatabi/midi_mapping_dialog.cpp | 3 +++ futatabi/midi_mapping_dialog.h | 1 + futatabi/player.cpp | 7 ++++++ futatabi/player.h | 6 +++++ 11 files changed, 79 insertions(+), 1 deletion(-) diff --git a/futatabi/behringer_cmd_pl1.midimapping b/futatabi/behringer_cmd_pl1.midimapping index 52777b9..3b6d58a 100644 --- a/futatabi/behringer_cmd_pl1.midimapping +++ b/futatabi/behringer_cmd_pl1.midimapping @@ -23,6 +23,10 @@ play: { note_number: 35 } play_ready: { note_number: 35 velocity: 2 } playing: { note_number: 35 } +# Next is mapped to fast-forward. +next: { note_number: 37 } +next_ready: { note_number: 37 } + # Queue is marked to Cue; close enough. queue: { note_number: 34 } queue_enabled: { note_number: 34 } diff --git a/futatabi/futatabi_midi_mapping.proto b/futatabi/futatabi_midi_mapping.proto index 8e6b3e9..1c7b749 100644 --- a/futatabi/futatabi_midi_mapping.proto +++ b/futatabi/futatabi_midi_mapping.proto @@ -50,6 +50,10 @@ message MIDIMappingProto { optional MIDILightProto playing = 26; optional MIDILightProto play_ready = 40; + optional MIDIButtonProto next = 45; + optional int32 next_button_bank = 46; + optional MIDILightProto next_ready = 47; + optional MIDIButtonProto toggle_lock = 36; optional int32 toggle_lock_bank = 37; optional MIDILightProto locked = 38; diff --git a/futatabi/mainwindow.cpp b/futatabi/mainwindow.cpp index a559293..6c52109 100644 --- a/futatabi/mainwindow.cpp +++ b/futatabi/mainwindow.cpp @@ -205,6 +205,10 @@ MainWindow::MainWindow() connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click); connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked); + QShortcut *next = new QShortcut(QKeySequence(Qt::Key_N), this); + connect(next, &QShortcut::activated, ui->next_btn, &QPushButton::click); + connect(ui->next_btn, &QPushButton::clicked, this, &MainWindow::next_clicked); + connect(ui->stop_btn, &QPushButton::clicked, this, &MainWindow::stop_clicked); ui->stop_btn->setEnabled(false); @@ -663,6 +667,11 @@ void MainWindow::play_clicked() playlist_selection_changed(); } +void MainWindow::next_clicked() +{ + live_player->skip_to_next(); +} + void MainWindow::stop_clicked() { Clip fake_clip; @@ -733,6 +742,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { enable_or_disable_preview_button(); + playlist_selection_changed(); hidden_jog_column = -1; } @@ -932,7 +942,7 @@ void MainWindow::playlist_selection_changed() enable_or_disable_preview_button(); QItemSelectionModel *selected = ui->playlist->selectionModel(); - bool any_selected = selected->hasSelection(); + bool any_selected = ui->playlist->hasFocus() && selected->hasSelection(); ui->playlist_duplicate_btn->setEnabled(any_selected); ui->playlist_remove_btn->setEnabled(any_selected); ui->playlist_move_up_btn->setEnabled( @@ -941,6 +951,19 @@ void MainWindow::playlist_selection_changed() any_selected && selected->selectedRows().back().row() < int(playlist_clips->size()) - 1); ui->play_btn->setEnabled(any_selected); + ui->next_btn->setEnabled(ui->stop_btn->isEnabled()); // TODO: Perhaps not if we're on the last clip? + midi_mapper.set_next_ready(ui->next_btn->isEnabled() ? MIDIMapper::On : MIDIMapper::Off); + + // NOTE: The hidden button is still reachable by keyboard or MIDI. + if (any_selected) { + ui->play_btn->setVisible(true); + } else if (ui->stop_btn->isEnabled()) { // Playing. + ui->play_btn->setVisible(false); + } else { + ui->play_btn->setVisible(true); + } + ui->next_btn->setVisible(!ui->play_btn->isVisible()); + if (ui->stop_btn->isEnabled()) { // Playing. midi_mapper.set_play_enabled(MIDIMapper::On); } else if (any_selected) { @@ -1288,6 +1311,13 @@ void MainWindow::play() }); } +void MainWindow::next() +{ + post_to_main_thread([this] { + next_clicked(); + }); +} + void MainWindow::toggle_lock() { post_to_main_thread([this] { diff --git a/futatabi/mainwindow.h b/futatabi/mainwindow.h index 2eeacc3..6e8f0c7 100644 --- a/futatabi/mainwindow.h +++ b/futatabi/mainwindow.h @@ -43,6 +43,7 @@ public: void preview() override; void queue() override; void play() override; + void next() override; void toggle_lock() override; void jog(int delta) override; void switch_camera(unsigned camera_idx) override; @@ -134,6 +135,7 @@ private: void preview_clicked(); void preview_angle_clicked(unsigned stream_idx); void play_clicked(); + void next_clicked(); void stop_clicked(); void speed_slider_changed(int percent); void speed_lock_clicked(); diff --git a/futatabi/mainwindow.ui b/futatabi/mainwindow.ui index c94b58c..cd0d007 100644 --- a/futatabi/mainwindow.ui +++ b/futatabi/mainwindow.ui @@ -211,6 +211,16 @@ + + + + Next (N) + + + + + + diff --git a/futatabi/midi_mapper.cpp b/futatabi/midi_mapper.cpp index 1d5d79e..9cfb3be 100644 --- a/futatabi/midi_mapper.cpp +++ b/futatabi/midi_mapper.cpp @@ -143,6 +143,8 @@ void MIDIMapper::note_on_received(int note) bind(&ControllerReceiver::queue, receiver)); match_button(note, MIDIMappingProto::kPlayFieldNumber, MIDIMappingProto::kPlayBankFieldNumber, bind(&ControllerReceiver::play, receiver)); + match_button(note, MIDIMappingProto::kNextFieldNumber, MIDIMappingProto::kNextButtonBankFieldNumber, + bind(&ControllerReceiver::next, receiver)); match_button(note, MIDIMappingProto::kToggleLockFieldNumber, MIDIMappingProto::kToggleLockBankFieldNumber, bind(&ControllerReceiver::toggle_lock, receiver)); @@ -236,6 +238,9 @@ void MIDIMapper::update_lights_lock_held() } else if (play_enabled_light == Blinking) { // Play ready. activate_mapped_light(*mapping_proto, MIDIMappingProto::kPlayReadyFieldNumber, &active_lights); } + if (next_ready_light == On) { + activate_mapped_light(*mapping_proto, MIDIMappingProto::kNextReadyFieldNumber, &active_lights); + } if (locked_light == On) { activate_mapped_light(*mapping_proto, MIDIMappingProto::kLockedFieldNumber, &active_lights); } else if (locked_light == Blinking) { diff --git a/futatabi/midi_mapper.h b/futatabi/midi_mapper.h index b3549fe..84ff404 100644 --- a/futatabi/midi_mapper.h +++ b/futatabi/midi_mapper.h @@ -31,6 +31,7 @@ public: virtual void preview() = 0; virtual void queue() = 0; virtual void play() = 0; + virtual void next() = 0; virtual void toggle_lock() = 0; virtual void jog(int delta) = 0; virtual void switch_camera(unsigned camera_idx) = 0; @@ -75,6 +76,10 @@ public: play_enabled_light = enabled; refresh_lights(); } + void set_next_ready(LightState enabled) { + next_ready_light = enabled; + refresh_lights(); + } void set_locked(LightState locked) { locked_light = locked; refresh_lights(); @@ -112,6 +117,7 @@ private: std::atomic preview_enabled_light{Off}; std::atomic queue_enabled_light{false}; std::atomic play_enabled_light{Off}; + std::atomic next_ready_light{Off}; std::atomic locked_light{On}; std::atomic current_highlighted_camera{-1}; std::atomic current_speed{1.0f}; diff --git a/futatabi/midi_mapping_dialog.cpp b/futatabi/midi_mapping_dialog.cpp index 30d562f..67b33d3 100644 --- a/futatabi/midi_mapping_dialog.cpp +++ b/futatabi/midi_mapping_dialog.cpp @@ -46,6 +46,8 @@ vector buttons = { MIDIMappingProto::kQueueBankFieldNumber }, { "Play", MIDIMappingProto::kPlayFieldNumber, MIDIMappingProto::kPlayBankFieldNumber }, + { "Next", MIDIMappingProto::kNextFieldNumber, + MIDIMappingProto::kNextButtonBankFieldNumber }, { "Lock master speed", MIDIMappingProto::kToggleLockFieldNumber, MIDIMappingProto::kToggleLockBankFieldNumber }, { "Cue in", MIDIMappingProto::kCueInFieldNumber, @@ -66,6 +68,7 @@ vector button_lights = { { "Queue button enabled", MIDIMappingProto::kQueueEnabledFieldNumber, 0 }, { "Playing", MIDIMappingProto::kPlayingFieldNumber, 0 }, { "Play ready", MIDIMappingProto::kPlayReadyFieldNumber, 0 }, + { "Next ready", MIDIMappingProto::kNextReadyFieldNumber, 0 }, { "Master speed locked", MIDIMappingProto::kLockedFieldNumber, 0 }, { "Master speed locked (blinking)", MIDIMappingProto::kLockedBlinkingFieldNumber, 0 }, diff --git a/futatabi/midi_mapping_dialog.h b/futatabi/midi_mapping_dialog.h index 6b3e944..d087796 100644 --- a/futatabi/midi_mapping_dialog.h +++ b/futatabi/midi_mapping_dialog.h @@ -43,6 +43,7 @@ public: void preview() override {} void queue() override {} void play() override {} + void next() override {} void toggle_lock() override {} void jog(int delta) override {} void switch_camera(unsigned camera_idx) override {} diff --git a/futatabi/player.cpp b/futatabi/player.cpp index 9c20278..876c8dc 100644 --- a/futatabi/player.cpp +++ b/futatabi/player.cpp @@ -151,6 +151,7 @@ void Player::play_playlist_once() return; } + should_skip_to_next = false; // To make sure we don't have a lingering click from before play. steady_clock::time_point origin = steady_clock::now(); // TODO: Add a 100 ms buffer for ramp-up? int64_t in_pts_origin = clip_list[0].clip.pts_in; for (size_t clip_idx = 0; clip_idx < clip_list.size(); ++clip_idx) { @@ -195,6 +196,12 @@ void Player::play_playlist_once() out_pts_origin = out_pts - TIMEBASE * frameno / global_flags.output_framerate; } + if (should_skip_to_next.exchange(false)) { // Test and clear. + Clip *clip = &clip_list[clip_idx].clip; // Get a non-const pointer. + fprintf(stderr, "pts_out moving to first of %ld and %ld (currently at %f)\n", clip->pts_out, lrint(out_pts + clip->fade_time_seconds * TIMEBASE), out_pts); + clip->pts_out = std::min(clip->pts_out, lrint(in_pts + clip->fade_time_seconds * clip->speed * TIMEBASE)); + } + if (in_pts >= clip->pts_out) { break; } diff --git a/futatabi/player.h b/futatabi/player.h index e64abd7..79ab64d 100644 --- a/futatabi/player.h +++ b/futatabi/player.h @@ -61,6 +61,11 @@ public: pause_status = status; } + void skip_to_next() + { + should_skip_to_next = true; + } + void set_master_speed(float speed) { change_master_speed = speed; @@ -95,6 +100,7 @@ private: std::thread player_thread; std::atomic should_quit{ false }; + std::atomic should_skip_to_next{ false }; std::atomic change_master_speed{ 0.0f / 0.0f }; JPEGFrameView *destination; -- 2.39.2