# but it's also unclear where else it would be.
preview: { note_number: 27 }
preview_enabled: { note_number: 27 }
+
+# The slider (pitch bend) is mapped to master speed.
+master_speed: { controller_number: 128 }
optional MIDIControllerProto jog = 16;
optional int32 jog_bank = 17;
+ optional MIDIControllerProto master_speed = 34;
+ optional int32 master_speed_bank = 35;
+
// Buttons.
optional MIDIButtonProto preview = 18;
optional int32 preview_bank = 19;
connect(ui->stop_btn, &QPushButton::clicked, this, &MainWindow::stop_clicked);
ui->stop_btn->setEnabled(false);
+ connect(ui->speed_slider, &QAbstractSlider::valueChanged, this, &MainWindow::speed_slider_changed);
+
connect(ui->playlist_duplicate_btn, &QPushButton::clicked, this, &MainWindow::playlist_duplicate);
connect(ui->playlist_remove_btn, &QPushButton::clicked, this, &MainWindow::playlist_remove);
ui->stop_btn->setEnabled(false);
}
+void MainWindow::speed_slider_changed(int percent)
+{
+ float speed = percent / 100.0f;
+ ui->speed_label->setText(QString::fromStdString(to_string(percent) + "%"));
+ live_player->set_master_speed(speed);
+}
+
void MainWindow::live_player_done()
{
playlist_selection_changed();
});
}
+void MainWindow::set_master_speed(float speed)
+{
+ speed = min(max(speed, 0.1f), 2.0f);
+
+ post_to_main_thread([this, speed] {
+ int percent = lrintf(speed * 100.0f);
+ ui->speed_slider->blockSignals(true);
+ ui->speed_slider->setValue(percent);
+ ui->speed_slider->blockSignals(false);
+ ui->speed_label->setText(QString::fromStdString(to_string(percent) + "%"));
+ });
+
+ live_player->set_master_speed(speed);
+}
+
void MainWindow::cue_in()
{
post_to_main_thread([this] { cue_in_clicked(); });
void play() override;
void jog(int delta) override;
void switch_camera(unsigned camera_idx) override;
+ void set_master_speed(float speed) override;
void cue_in() override;
void cue_out() override;
void preview_angle_clicked(unsigned stream_idx);
void play_clicked();
void stop_clicked();
+ void speed_slider_changed(int percent);
void live_player_done();
void live_player_clip_progress(const std::map<uint64_t, double> &progress, double time_remaining);
void set_output_status(const std::string &status);
</widget>
</item>
<item>
- <spacer name="horizontalSpacer">
+ <widget class="QSlider" name="speed_slider">
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
- <property name="sizeHint" stdset="0">
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="speed_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
<size>
<width>40</width>
- <height>20</height>
+ <height>0</height>
</size>
</property>
- </spacer>
+ <property name="text">
+ <string>100%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
</item>
<item>
<widget class="QPushButton" name="stop_btn">
void MIDIMapper::controller_received(int controller, int value_int)
{
int delta_value = value_int - 64; // For infinite controllers such as jog.
+ float value = map_controller_to_float(controller, value_int);
receiver->controller_changed(controller);
match_controller(controller, MIDIMappingProto::kJogFieldNumber, MIDIMappingProto::kJogBankFieldNumber,
delta_value, bind(&ControllerReceiver::jog, receiver, _1));
+
+ // Speed goes from 0.0 to 2.0 (the receiver will clamp).
+ match_controller(controller, MIDIMappingProto::kMasterSpeedFieldNumber, MIDIMappingProto::kMasterSpeedBankFieldNumber,
+ value * 2.0, bind(&ControllerReceiver::set_master_speed, receiver, _1));
}
void MIDIMapper::note_on_received(int note)
virtual void play() = 0;
virtual void jog(int delta) = 0;
virtual void switch_camera(unsigned camera_idx) = 0;
+ virtual void set_master_speed(float speed) = 0;
virtual void cue_in() = 0;
virtual void cue_out() = 0;
bool clip_ready;
steady_clock::time_point before_sleep = steady_clock::now();
string pause_status;
+ float master_speed = 1.0f;
// Wait until we're supposed to play something.
{
double out_pts = out_pts_origin + TIMEBASE * frameno / global_flags.output_framerate;
next_frame_start =
origin + microseconds(lrint((out_pts - out_pts_origin) * 1e6 / TIMEBASE));
- int64_t in_pts = lrint(in_pts_origin + TIMEBASE * frameno * clip->speed / global_flags.output_framerate);
+ int64_t in_pts = lrint(in_pts_origin + TIMEBASE * frameno * clip->speed * master_speed / global_flags.output_framerate);
pts = lrint(out_pts);
+ float new_master_speed = change_master_speed.exchange(0.0f / 0.0f);
+ if (!std::isnan(new_master_speed)) {
+ master_speed = new_master_speed;
+ in_pts_origin = in_pts - TIMEBASE * frameno * clip->speed * master_speed / global_flags.output_framerate;
+ out_pts_origin = out_pts - TIMEBASE * frameno / global_flags.output_framerate;
+ }
+
if (in_pts >= clip->pts_out) {
break;
}
pause_status = status;
}
+ void set_master_speed(float speed)
+ {
+ change_master_speed = speed;
+ }
+
// Not thread-safe to set concurrently with playing.
// Will be called back from the player thread.
using done_callback_func = std::function<void()>;
std::thread player_thread;
std::atomic<bool> should_quit{ false };
+ std::atomic<float> change_master_speed{ 0.0f / 0.0f };
JPEGFrameView *destination;
done_callback_func done_callback;
using namespace std;
using namespace std::placeholders;
-namespace {
-
-double map_controller_to_float(int controller, int val)
-{
- if (controller == MIDIReceiver::PITCH_BEND_CONTROLLER) {
- // We supposedly go from -8192 to 8191 (inclusive), but there are
- // controllers that only have 10-bit precision and do the upconversion
- // to 14-bit wrong (just padding with zeros), making 8176 the highest
- // attainable value. We solve this by making the effective range
- // -8176..8176 (inclusive).
- if (val <= -8176) {
- return 0.0;
- } else if (val >= 8176) {
- return 1.0;
- } else {
- return 0.5 * (double(val) / 8176.0) + 0.5;
- }
- }
-
- // Slightly hackish mapping so that we can represent exactly 0.0, 0.5 and 1.0.
- if (val <= 0) {
- return 0.0;
- } else if (val >= 127) {
- return 1.0;
- } else {
- return (val + 0.5) / 127.0;
- }
-}
-
-} // namespace
-
MIDIMapper::MIDIMapper(ControllerReceiver *receiver)
: receiver(receiver), mapping_proto(new MIDIMappingProto), midi_device(this)
{
#define _MIDI_MAPPER_UTIL_H 1
#include "midi_mapping.pb.h"
+#include "shared/midi_device.h"
#include <google/protobuf/descriptor.h>
active_lights->insert(light_proto.note_number());
}
+inline double map_controller_to_float(int controller, int val)
+{
+ if (controller == MIDIReceiver::PITCH_BEND_CONTROLLER) {
+ // We supposedly go from -8192 to 8191 (inclusive), but there are
+ // controllers that only have 10-bit precision and do the upconversion
+ // to 14-bit wrong (just padding with zeros), making 8176 the highest
+ // attainable value. We solve this by making the effective range
+ // -8176..8176 (inclusive).
+ if (val <= -8176) {
+ return 0.0;
+ } else if (val >= 8176) {
+ return 1.0;
+ } else {
+ return 0.5 * (double(val) / 8176.0) + 0.5;
+ }
+ }
+
+ // Slightly hackish mapping so that we can represent exactly 0.0, 0.5 and 1.0.
+ if (val <= 0) {
+ return 0.0;
+ } else if (val >= 127) {
+ return 1.0;
+ } else {
+ return (val + 0.5) / 127.0;
+ }
+}
+
#endif // !defined(_MIDI_MAPPER_UTIL_H)