]> git.sesse.net Git - nageru/commitdiff
Add support for aborting playing a clip early.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 13 Feb 2019 22:41:50 +0000 (23:41 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 13 Feb 2019 22:41:50 +0000 (23:41 +0100)
futatabi/behringer_cmd_pl1.midimapping
futatabi/futatabi_midi_mapping.proto
futatabi/mainwindow.cpp
futatabi/mainwindow.h
futatabi/mainwindow.ui
futatabi/midi_mapper.cpp
futatabi/midi_mapper.h
futatabi/midi_mapping_dialog.cpp
futatabi/midi_mapping_dialog.h
futatabi/player.cpp
futatabi/player.h

index 52777b9498e37d82e8fd16823563727bdeaaba23..3b6d58a7d5e8d42da582ed24d4cc7dade5963e13 100644 (file)
@@ -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 }
index 8e6b3e9d32b78182e7e89c76a70eb60bcdb25348..1c7b749c4542b8794257062fdf750c36bccc21dc 100644 (file)
@@ -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;
index a5592930e2a3217df0b980db3d65165a362c4781..6c52109c07701efc0d9a5d5dcbddaba94c4f401b 100644 (file)
@@ -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] {
index 2eeacc3c0ff31e4a521a68e4e4382b00ea60c88c..6e8f0c7b3dbe0b8c5ab7c6da0bd87d18f9c51ce5 100644 (file)
@@ -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();
index c94b58c8d20ab11a0eab9f7f139bd92365266c33..cd0d007f06b7dacaa45cb40819913158dd18c82e 100644 (file)
             </property>
            </widget>
           </item>
+          <item>
+           <widget class="QPushButton" name="next_btn">
+            <property name="text">
+             <string>Next (N)</string>
+            </property>
+            <property name="icon">
+             <iconset theme="media-skip-forward"/>
+            </property>
+           </widget>
+          </item>
          </layout>
         </item>
        </layout>
index 1d5d79e4ba85e9d9388f73bd94ecdc60d7840281..9cfb3be6749e2f9234fe81c2fde5fb6bbd926da1 100644 (file)
@@ -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) {
index b3549feffe54e15cdfa068beb12eb12ab325f455..84ff404b1f0b956830a475f4cf18318680a5e461 100644 (file)
@@ -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<LightState> preview_enabled_light{Off};
        std::atomic<bool> queue_enabled_light{false};
        std::atomic<LightState> play_enabled_light{Off};
+       std::atomic<LightState> next_ready_light{Off};
        std::atomic<LightState> locked_light{On};
        std::atomic<int> current_highlighted_camera{-1};
        std::atomic<float> current_speed{1.0f};
index 30d562f41441e56faaacfdd2b4eb8a52f4033841..67b33d3c1eb37b9f314ce801db91e69ec3efd0d1 100644 (file)
@@ -46,6 +46,8 @@ vector<MIDIMappingDialog::Control> 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<MIDIMappingDialog::Control> 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 },
index 6b3e944423aac7711221f8d6db7a60ddfb28605b..d087796cc2ac329bacb56b2fe22eb7f99d2123bb 100644 (file)
@@ -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 {}
index 9c20278702abcb4a50a6284068667b0dad102602..876c8dcac6b95684e54a4cfb3f354eed8331806c 100644 (file)
@@ -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;
                        }
index e64abd79f7b834b0f6521829576da8e4ef8bc9f5..79ab64db4a1788430aa9658287db9621119b0001 100644 (file)
@@ -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<bool> should_quit{ false };
+       std::atomic<bool> should_skip_to_next{ false };
        std::atomic<float> change_master_speed{ 0.0f / 0.0f };
 
        JPEGFrameView *destination;