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 }
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;
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);
playlist_selection_changed();
}
+void MainWindow::next_clicked()
+{
+ live_player->skip_to_next();
+}
+
void MainWindow::stop_clicked()
{
Clip fake_clip;
if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) {
enable_or_disable_preview_button();
+ playlist_selection_changed();
hidden_jog_column = -1;
}
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(
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) {
});
}
+void MainWindow::next()
+{
+ post_to_main_thread([this] {
+ next_clicked();
+ });
+}
+
void MainWindow::toggle_lock()
{
post_to_main_thread([this] {
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;
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();
</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>
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));
} 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) {
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;
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();
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};
MIDIMappingProto::kQueueBankFieldNumber },
{ "Play", MIDIMappingProto::kPlayFieldNumber,
MIDIMappingProto::kPlayBankFieldNumber },
+ { "Next", MIDIMappingProto::kNextFieldNumber,
+ MIDIMappingProto::kNextButtonBankFieldNumber },
{ "Lock master speed", MIDIMappingProto::kToggleLockFieldNumber,
MIDIMappingProto::kToggleLockBankFieldNumber },
{ "Cue in", MIDIMappingProto::kCueInFieldNumber,
{ "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 },
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 {}
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) {
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;
}
pause_status = status;
}
+ void skip_to_next()
+ {
+ should_skip_to_next = true;
+ }
+
void set_master_speed(float speed)
{
change_master_speed = speed;
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;