return input_mapping;
}
+unsigned AudioMixer::num_buses() const
+{
+ lock_guard<timed_mutex> lock(audio_mutex);
+ return input_mapping.buses.size();
+}
+
void AudioMixer::reset_peak(unsigned bus_index)
{
lock_guard<timed_mutex> lock(audio_mutex);
MappingMode get_mapping_mode() const;
InputMapping get_input_mapping() const;
+ unsigned num_buses() const;
+
void set_locut_cutoff(float cutoff_hz)
{
locut_cutoff_hz = cutoff_hz;
MainWindow *global_mainwindow = nullptr;
+// -0.1 dBFS is EBU peak limit. We use it consistently, even for the bus meters
+// (which don't calculate interpolate peak, and in general don't follow EBU recommendations).
+constexpr float peak_limit_dbfs = -0.1f;
+
namespace {
void schedule_cut_signal(int ignored)
{
peak_label->setText(QString::fromStdString(format_db(peak_db, DB_BARE)));
- // -0.1 dBFS is EBU peak limit. We use it consistently, even for the bus meters
- // (which don't calculate interpolate peak, and in general don't follow EBU recommendations).
- if (peak_db > -0.1f) {
+ if (peak_db > peak_limit_dbfs) {
peak_label->setStyleSheet("QLabel { background-color: red; color: white; }");
} else {
peak_label->setStyleSheet("");
}
connect(ui->locut_enabled, &QCheckBox::stateChanged, [this](int state){
global_audio_mixer->set_locut_enabled(simple_bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged,
bind(&MainWindow::gain_staging_knob_changed, this, simple_bus_index, _1));
connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this, simple_bus_index](int state){
global_audio_mixer->set_gain_staging_auto(simple_bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui->compressor_threshold_knob, &QDial::valueChanged,
bind(&MainWindow::compressor_threshold_knob_changed, this, simple_bus_index, _1));
connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this, simple_bus_index](int state){
global_audio_mixer->set_compressor_enabled(simple_bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
// Global mastering controls.
connect(ui->makeup_gain_knob, &QAbstractSlider::valueChanged, this, &MainWindow::final_makeup_gain_knob_changed);
connect(ui->makeup_gain_auto_checkbox, &QCheckBox::stateChanged, [this](int state){
global_audio_mixer->set_final_makeup_gain_auto(state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui->limiter_threshold_knob, &QDial::valueChanged, this, &MainWindow::limiter_threshold_knob_changed);
connect(ui->limiter_enabled, &QCheckBox::stateChanged, [this](int state){
global_audio_mixer->set_limiter_enabled(state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui->reset_meters_button, &QPushButton::clicked, this, &MainWindow::reset_meters_button_clicked);
mixer->get_audio_mixer()->set_audio_level_callback(bind(&MainWindow::audio_level_callback, this, _1, _2, _3, _4, _5, _6, _7, _8));
midi_mapper.refresh_highlights();
+ midi_mapper.refresh_lights();
struct sigaction act;
memset(&act, 0, sizeof(act));
ui->compact_header->setVisible(!simple);
midi_mapper.refresh_highlights();
+ midi_mapper.refresh_lights();
}
void MainWindow::setup_audio_miniview()
ui_audio_expanded_view->locut_enabled->setChecked(global_audio_mixer->get_locut_enabled(bus_index));
connect(ui_audio_expanded_view->locut_enabled, &QCheckBox::stateChanged, [this, bus_index](int state){
global_audio_mixer->set_locut_enabled(bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui_audio_expanded_view->treble_knob, &QDial::valueChanged,
connect(ui_audio_expanded_view->gainstaging_knob, &QAbstractSlider::valueChanged, bind(&MainWindow::gain_staging_knob_changed, this, bus_index, _1));
connect(ui_audio_expanded_view->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this, bus_index](int state){
global_audio_mixer->set_gain_staging_auto(bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
connect(ui_audio_expanded_view->compressor_threshold_knob, &QDial::valueChanged, bind(&MainWindow::compressor_threshold_knob_changed, this, bus_index, _1));
connect(ui_audio_expanded_view->compressor_enabled, &QCheckBox::stateChanged, [this, bus_index](int state){
global_audio_mixer->set_compressor_enabled(bus_index, state == Qt::Checked);
+ midi_mapper.refresh_lights();
});
slave_fader(audio_miniviews[bus_index]->fader, ui_audio_expanded_view->fader);
peak_meter->set_ref_level(0.0f);
connect(ui_audio_expanded_view->peak_display_label, &ClickableLabel::clicked,
- [bus_index]() {
+ [this, bus_index]() {
global_audio_mixer->reset_peak(bus_index);
+ midi_mapper.refresh_lights();
});
// Set up the compression attenuation meter.
setup_audio_expanded_view();
}
midi_mapper.refresh_highlights();
+ midi_mapper.refresh_lights();
}
void MainWindow::midi_mapping_triggered()
QString("Gain: ") +
QString::fromStdString(format_db(level.gain_staging_db, DB_WITH_SIGN)));
set_peak_label(view->peak_display_label, level.historic_peak_dbfs);
+
+ midi_mapper.set_has_peaked(bus_index, level.historic_peak_dbfs >= -0.1f);
}
}
ui->lra_meter->set_levels(global_level_lufs, range_low_lufs, range_high_lufs);
QString::fromStdString(format_db(final_makeup_gain_db, DB_WITH_SIGN)));
ui->makeup_gain_db_display_2->setText(
QString::fromStdString(format_db(final_makeup_gain_db, DB_WITH_SIGN)));
+
+ // Peak labels could have changed.
+ midi_mapper.refresh_lights();
});
}
void MainWindow::clear_peak(unsigned bus_idx)
{
- if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) {
- global_audio_mixer->reset_peak(bus_idx);
- }
+ post_to_main_thread([=]{
+ if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) {
+ global_audio_mixer->reset_peak(bus_idx);
+ midi_mapper.set_has_peaked(bus_idx, false);
+ midi_mapper.refresh_lights();
+ }
+ });
}
void MainWindow::clear_all_highlights()
template<class T>
void MainWindow::set_relative_value_if_exists(unsigned bus_idx, T *(Ui_AudioExpandedView::*control), float value)
{
- if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL &&
+ if (global_audio_mixer != nullptr &&
+ global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL &&
bus_idx < audio_expanded_views.size()) {
set_relative_value(audio_expanded_views[bus_idx]->*control, value);
}
template<class T>
void MainWindow::click_button_if_exists(unsigned bus_idx, T *(Ui_AudioExpandedView::*control))
{
- if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL &&
- bus_idx < audio_expanded_views.size()) {
- (audio_expanded_views[bus_idx]->*control)->click();
- }
+ post_to_main_thread([this, bus_idx, control]{
+ if (global_audio_mixer != nullptr &&
+ global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL &&
+ bus_idx < audio_expanded_views.size()) {
+ (audio_expanded_views[bus_idx]->*control)->click();
+ }
+ });
}
template<class T>
#include "midi_mapper.h"
+
+#include "audio_mixer.h"
#include "midi_mapping.pb.h"
#include <alsa/asoundlib.h>
void MIDIMapper::set_midi_mapping(const MIDIMappingProto &new_mapping)
{
- lock_guard<mutex> lock(mapping_mu);
+ lock_guard<mutex> lock(mu);
if (mapping_proto) {
mapping_proto->CopyFrom(new_mapping);
} else {
const MIDIMappingProto &MIDIMapper::get_current_mapping() const
{
- lock_guard<mutex> lock(mapping_mu);
+ lock_guard<mutex> lock(mu);
return *mapping_proto;
}
ControllerReceiver *MIDIMapper::set_receiver(ControllerReceiver *new_receiver)
{
- lock_guard<mutex> lock(mapping_mu);
+ lock_guard<mutex> lock(mu);
swap(receiver, new_receiver);
return new_receiver; // Now old receiver.
}
} \
} while (false)
+#define WARN_ON_ERROR(msg, expr) do { \
+ int err = (expr); \
+ if (err < 0) { \
+ fprintf(stderr, msg ": %s\n", snd_strerror(err)); \
+ } \
+} while (false)
+
void MIDIMapper::thread_func()
{
RETURN_ON_ERROR("snd_seq_client_name", snd_seq_set_client_name(seq, "nageru"));
RETURN_ON_ERROR("snd_seq_create_simple_port",
snd_seq_create_simple_port(seq, "nageru",
- SND_SEQ_PORT_CAP_WRITE |
- SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_SUBS_READ |
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
- SND_SEQ_PORT_TYPE_APPLICATION));
+ SND_SEQ_PORT_TYPE_APPLICATION));
+
+ int queue_id = snd_seq_alloc_queue(seq);
+ RETURN_ON_ERROR("snd_seq_create_queue", queue_id);
+ RETURN_ON_ERROR("snd_seq_start_queue", snd_seq_start_queue(seq, queue_id, nullptr));
+
+ // The sequencer object is now ready to be used from other threads.
+ {
+ lock_guard<mutex> lock(mu);
+ alsa_seq = seq;
+ alsa_queue_id = queue_id;
+ }
// Listen to the announce port (0:1), which will tell us about new ports.
RETURN_ON_ERROR("snd_seq_connect_from", snd_seq_connect_from(seq, 0, /*client=*/0, /*port=*/1));
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
constexpr int mask = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
if ((snd_seq_port_info_get_capability(pinfo) & mask) == mask) {
- subscribe_to_port(seq, *snd_seq_port_info_get_addr(pinfo));
+ lock_guard<mutex> lock(mu);
+ subscribe_to_port_lock_held(seq, *snd_seq_port_info_get_addr(pinfo));
}
}
}
void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event)
{
- lock_guard<mutex> lock(mapping_mu);
+ if (event->source.client == snd_seq_client_id(seq)) {
+ // Ignore events we sent out ourselves.
+ return;
+ }
+
+ lock_guard<mutex> lock(mu);
switch (event->type) {
case SND_SEQ_EVENT_CONTROLLER: {
printf("Controller %d changed to %d\n", event->data.control.param, event->data.control.value);
bus_mapping.prev_bank().note_number() == note) {
current_controller_bank = (current_controller_bank + num_controller_banks - 1) % num_controller_banks;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_next_bank() &&
bus_mapping.next_bank().note_number() == note) {
current_controller_bank = (current_controller_bank + 1) % num_controller_banks;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_select_bank_1() &&
bus_mapping.select_bank_1().note_number() == note) {
current_controller_bank = 0;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_select_bank_2() &&
bus_mapping.select_bank_2().note_number() == note &&
num_controller_banks >= 2) {
current_controller_bank = 1;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_select_bank_3() &&
bus_mapping.select_bank_3().note_number() == note &&
num_controller_banks >= 3) {
current_controller_bank = 2;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_select_bank_4() &&
bus_mapping.select_bank_4().note_number() == note &&
num_controller_banks >= 4) {
current_controller_bank = 3;
update_highlights();
+ update_lights_lock_held();
}
if (bus_mapping.has_select_bank_5() &&
bus_mapping.select_bank_5().note_number() == note &&
num_controller_banks >= 5) {
current_controller_bank = 4;
update_highlights();
+ update_lights_lock_held();
}
}
bind(&ControllerReceiver::toggle_auto_makeup_gain, receiver));
}
case SND_SEQ_EVENT_PORT_START:
- subscribe_to_port(seq, event->data.addr);
+ subscribe_to_port_lock_held(seq, event->data.addr);
break;
case SND_SEQ_EVENT_PORT_EXIT:
printf("MIDI port %d:%d went away.\n", event->data.addr.client, event->data.addr.port);
}
}
-void MIDIMapper::subscribe_to_port(snd_seq_t *seq, const snd_seq_addr_t &addr)
+void MIDIMapper::subscribe_to_port_lock_held(snd_seq_t *seq, const snd_seq_addr_t &addr)
{
// Client 0 is basically the system; ignore it.
if (addr.client == 0) {
} else {
printf("Subscribed to MIDI port %d:%d.\n", addr.client, addr.port);
}
+
+ // For sending data back.
+ err = snd_seq_connect_to(seq, 0, addr.client, addr.port);
+ if (err < 0) {
+ printf("Couldn't subscribe MIDI port %d:%d (%s) to us.\n",
+ addr.client, addr.port, snd_strerror(err));
+ } else {
+ printf("Subscribed MIDI port %d:%d to us.\n", addr.client, addr.port);
+ }
+
+ current_light_status.clear(); // The current state of the device is unknown.
+ update_lights_lock_held();
}
void MIDIMapper::match_controller(int controller, int field_number, int bank_field_number, float value, function<void(unsigned, float)> func)
update_highlights();
}
+void MIDIMapper::refresh_lights()
+{
+ lock_guard<mutex> lock(mu);
+ update_lights_lock_held();
+}
+
void MIDIMapper::update_highlights()
{
// Global controllers.
bus_idx, MIDIMappingBusProto::kToggleCompressorFieldNumber, MIDIMappingProto::kToggleCompressorBankFieldNumber));
}
}
+
+void MIDIMapper::update_lights_lock_held()
+{
+ if (alsa_seq == nullptr || global_audio_mixer == nullptr) {
+ return;
+ }
+
+ set<unsigned> active_lights; // Desired state.
+ if (current_controller_bank == 0) {
+ activate_lights_all_buses(MIDIMappingBusProto::kBank1IsSelectedFieldNumber, &active_lights);
+ }
+ if (current_controller_bank == 1) {
+ activate_lights_all_buses(MIDIMappingBusProto::kBank2IsSelectedFieldNumber, &active_lights);
+ }
+ if (current_controller_bank == 2) {
+ activate_lights_all_buses(MIDIMappingBusProto::kBank3IsSelectedFieldNumber, &active_lights);
+ }
+ if (current_controller_bank == 3) {
+ activate_lights_all_buses(MIDIMappingBusProto::kBank4IsSelectedFieldNumber, &active_lights);
+ }
+ if (current_controller_bank == 4) {
+ activate_lights_all_buses(MIDIMappingBusProto::kBank5IsSelectedFieldNumber, &active_lights);
+ }
+ if (global_audio_mixer->get_limiter_enabled()) {
+ activate_lights_all_buses(MIDIMappingBusProto::kLimiterIsOnFieldNumber, &active_lights);
+ }
+ if (global_audio_mixer->get_final_makeup_gain_auto()) {
+ activate_lights_all_buses(MIDIMappingBusProto::kAutoMakeupGainIsOnFieldNumber, &active_lights);
+ }
+ unsigned num_buses = min<unsigned>(global_audio_mixer->num_buses(), mapping_proto->bus_mapping_size());
+ for (unsigned bus_idx = 0; bus_idx < num_buses; ++bus_idx) {
+ if (global_audio_mixer->get_locut_enabled(bus_idx)) {
+ activate_lights(bus_idx, MIDIMappingBusProto::kLocutIsOnFieldNumber, &active_lights);
+ }
+ if (global_audio_mixer->get_gain_staging_auto(bus_idx)) {
+ activate_lights(bus_idx, MIDIMappingBusProto::kAutoGainStagingIsOnFieldNumber, &active_lights);
+ }
+ if (global_audio_mixer->get_compressor_enabled(bus_idx)) {
+ activate_lights(bus_idx, MIDIMappingBusProto::kCompressorIsOnFieldNumber, &active_lights);
+ }
+ if (has_peaked[bus_idx]) {
+ activate_lights(bus_idx, MIDIMappingBusProto::kHasPeakedFieldNumber, &active_lights);
+ }
+ }
+
+ unsigned num_events = 0;
+ for (unsigned note_num = 1; note_num <= 127; ++note_num) {
+ bool active = active_lights.count(note_num);
+ if (current_light_status.count(note_num) &&
+ current_light_status[note_num] == active) {
+ // Already known to be in the desired state.
+ continue;
+ }
+
+ snd_seq_event_t ev;
+ snd_seq_ev_clear(&ev);
+
+ // Some devices drop events if we throw them onto them
+ // too quickly. Add a 1 ms delay for each.
+ snd_seq_real_time_t tm{0, num_events++ * 1000000};
+ snd_seq_ev_schedule_real(&ev, alsa_queue_id, true, &tm);
+ snd_seq_ev_set_source(&ev, 0);
+ snd_seq_ev_set_subs(&ev);
+
+ // For some reason, not all devices respond to note off.
+ // Use note-on with velocity of 0 (which is equivalent) instead.
+ snd_seq_ev_set_noteon(&ev, /*channel=*/0, note_num, active ? 127 : 0);
+ WARN_ON_ERROR("snd_seq_event_output", snd_seq_event_output(alsa_seq, &ev));
+ current_light_status[note_num] = active;
+ }
+ WARN_ON_ERROR("snd_seq_drain_output", snd_seq_drain_output(alsa_seq));
+}
+
+void MIDIMapper::activate_lights(unsigned bus_idx, int field_number, set<unsigned> *active_lights)
+{
+ const MIDIMappingBusProto &bus_mapping = mapping_proto->bus_mapping(bus_idx);
+
+ const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number);
+ const Reflection *bus_reflection = bus_mapping.GetReflection();
+ if (!bus_reflection->HasField(bus_mapping, descriptor)) {
+ return;
+ }
+ const MIDILightProto &light_proto =
+ static_cast<const MIDILightProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
+ active_lights->insert(light_proto.note_number());
+}
+
+void MIDIMapper::activate_lights_all_buses(int field_number, set<unsigned> *active_lights)
+{
+ for (size_t bus_idx = 0; bus_idx < size_t(mapping_proto->bus_mapping_size()); ++bus_idx) {
+ const MIDIMappingBusProto &bus_mapping = mapping_proto->bus_mapping(bus_idx);
+
+ const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number);
+ const Reflection *bus_reflection = bus_mapping.GetReflection();
+ if (!bus_reflection->HasField(bus_mapping, descriptor)) {
+ continue;
+ }
+ const MIDILightProto &light_proto =
+ static_cast<const MIDILightProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
+ active_lights->insert(light_proto.note_number());
+ }
+}
#include <atomic>
#include <functional>
+#include <map>
#include <memory>
#include <mutex>
+#include <set>
#include <string>
#include <thread>
+#include "defs.h"
+
class MIDIMappingProto;
typedef struct snd_seq_addr snd_seq_addr_t;
typedef struct snd_seq_event snd_seq_event_t;
ControllerReceiver *set_receiver(ControllerReceiver *new_receiver);
void refresh_highlights();
+ void refresh_lights();
+
+ void set_has_peaked(unsigned bus_idx, bool has_peaked)
+ {
+ this->has_peaked[bus_idx] = has_peaked;
+ }
private:
void thread_func();
void handle_event(snd_seq_t *seq, snd_seq_event_t *event);
- void subscribe_to_port(snd_seq_t *seq, const snd_seq_addr_t &addr);
+ void subscribe_to_port_lock_held(snd_seq_t *seq, const snd_seq_addr_t &addr);
void match_controller(int controller, int field_number, int bank_field_number, float value, std::function<void(unsigned, float)> func);
void match_button(int note, int field_number, int bank_field_number, std::function<void(unsigned)> func);
bool has_active_controller(unsigned bus_idx, int field_number, int bank_field_number); // Also works for buttons.
void update_highlights();
+ void update_lights_lock_held();
+ void activate_lights(unsigned bus_idx, int field_number, std::set<unsigned> *active_lights);
+ void activate_lights_all_buses(int field_number, std::set<unsigned> *active_lights);
+
std::atomic<bool> should_quit{false};
int should_quit_fd;
- mutable std::mutex mapping_mu;
- ControllerReceiver *receiver; // Under <mapping_mu>.
- std::unique_ptr<MIDIMappingProto> mapping_proto; // Under <mapping_mu>.
- int num_controller_banks; // Under <mapping_mu>.
+ std::atomic<bool> has_peaked[MAX_BUSES] {{ false }};
+
+ mutable std::mutex mu;
+ ControllerReceiver *receiver; // Under <mu>.
+ std::unique_ptr<MIDIMappingProto> mapping_proto; // Under <mu>.
+ int num_controller_banks; // Under <mu>.
std::atomic<int> current_controller_bank{0};
std::thread midi_thread;
+ std::map<unsigned, bool> current_light_status; // Keyed by note number. Under <mu>.
+ snd_seq_t *alsa_seq{nullptr}; // Under <mu>.
+ int alsa_queue_id{-1}; // Under <mu>.
};
bool load_midi_mapping_from_file(const std::string &filename, MIDIMappingProto *new_mapping);
required int32 note_number = 1;
}
+message MIDILightProto {
+ required int32 note_number = 1;
+}
+
// All the mappings for a given a bus.
message MIDIMappingBusProto {
// TODO: If we need support for lots of buses (i.e., more than the typical eight
optional MIDIControllerProto locut = 21;
optional MIDIControllerProto limiter_threshold = 22;
optional MIDIControllerProto makeup_gain = 23;
+
+ // Per-bus lights.
+ optional MIDILightProto locut_is_on = 24;
+ optional MIDILightProto auto_gain_staging_is_on = 25;
+ optional MIDILightProto compressor_is_on = 26;
+ optional MIDILightProto has_peaked = 27;
+
+ // Global lights. Same logic as above for why they're in this proto.
+ optional MIDILightProto bank_1_is_selected = 28;
+ optional MIDILightProto bank_2_is_selected = 29;
+ optional MIDILightProto bank_3_is_selected = 30;
+ optional MIDILightProto bank_4_is_selected = 31;
+ optional MIDILightProto bank_5_is_selected = 32;
+ optional MIDILightProto limiter_is_on = 33;
+ optional MIDILightProto auto_makeup_gain_is_on = 34;
}
// The top-level protobuf, containing all the bus mappings, as well as
{ "Clear peak", MIDIMappingBusProto::kClearPeakFieldNumber,
MIDIMappingProto::kClearPeakBankFieldNumber }
};
+vector<MIDIMappingDialog::Control> per_bus_lights = {
+ { "Locut is on", MIDIMappingBusProto::kLocutIsOnFieldNumber, 0 },
+ { "Auto gain staging is on", MIDIMappingBusProto::kAutoGainStagingIsOnFieldNumber, 0 },
+ { "Compressor is on", MIDIMappingBusProto::kCompressorIsOnFieldNumber, 0 },
+ { "Bus has peaked", MIDIMappingBusProto::kHasPeakedFieldNumber, 0 }
+};
vector<MIDIMappingDialog::Control> global_controllers = {
{ "Locut cutoff", MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber },
{ "Limiter threshold", MIDIMappingBusProto::kLimiterThresholdFieldNumber,
{ "Toggle limiter", MIDIMappingBusProto::kToggleLimiterFieldNumber, MIDIMappingProto::kToggleLimiterBankFieldNumber },
{ "Toggle auto makeup gain", MIDIMappingBusProto::kToggleAutoMakeupGainFieldNumber, MIDIMappingProto::kToggleAutoMakeupGainBankFieldNumber }
};
+vector<MIDIMappingDialog::Control> global_lights = {
+ { "Bank 1 is selected", MIDIMappingBusProto::kBank1IsSelectedFieldNumber, 0 },
+ { "Bank 2 is selected", MIDIMappingBusProto::kBank2IsSelectedFieldNumber, 0 },
+ { "Bank 3 is selected", MIDIMappingBusProto::kBank3IsSelectedFieldNumber, 0 },
+ { "Bank 4 is selected", MIDIMappingBusProto::kBank4IsSelectedFieldNumber, 0 },
+ { "Bank 5 is selected", MIDIMappingBusProto::kBank5IsSelectedFieldNumber, 0 },
+ { "Limiter is on", MIDIMappingBusProto::kLimiterIsOnFieldNumber, 0 },
+ { "Auto makeup gain is on", MIDIMappingBusProto::kAutoMakeupGainIsOnFieldNumber, 0 },
+};
namespace {
if (!bus_reflection->HasField(bus_mapping, descriptor)) {
return default_value;
}
- const MIDIControllerProto &controller_proto =
+ const MIDIControllerProto &controller_proto =
static_cast<const MIDIControllerProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
return controller_proto.controller_number();
}
if (!bus_reflection->HasField(bus_mapping, descriptor)) {
return default_value;
}
- const MIDIButtonProto &bus_proto =
+ const MIDIButtonProto &bus_proto =
static_cast<const MIDIButtonProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
return bus_proto.note_number();
}
+int get_light_mapping(const MIDIMappingProto &mapping_proto, size_t bus_idx, int field_number, int default_value)
+{
+ if (bus_idx >= size_t(mapping_proto.bus_mapping_size())) {
+ return default_value;
+ }
+
+ const MIDIMappingBusProto &bus_mapping = mapping_proto.bus_mapping(bus_idx);
+ const FieldDescriptor *descriptor = bus_mapping.GetDescriptor()->FindFieldByNumber(field_number);
+ const Reflection *bus_reflection = bus_mapping.GetReflection();
+ if (!bus_reflection->HasField(bus_mapping, descriptor)) {
+ return default_value;
+ }
+ const MIDILightProto &bus_proto =
+ static_cast<const MIDILightProto &>(bus_reflection->GetMessage(bus_mapping, descriptor));
+ return bus_proto.note_number();
+}
+
} // namespace
MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
add_controls("Per-bus controllers", ControlType::CONTROLLER, SpinnerGroup::PER_BUS_CONTROLLERS, mapping_proto, per_bus_controllers);
add_controls("Per-bus buttons", ControlType::BUTTON, SpinnerGroup::PER_BUS_BUTTONS, mapping_proto, per_bus_buttons);
+ add_controls("Per-bus lights", ControlType::LIGHT, SpinnerGroup::PER_BUS_LIGHTS, mapping_proto, per_bus_lights);
add_controls("Global controllers", ControlType::CONTROLLER, SpinnerGroup::GLOBAL_CONTROLLERS, mapping_proto, global_controllers);
add_controls("Global buttons", ControlType::BUTTON, SpinnerGroup::GLOBAL_BUTTONS, mapping_proto, global_buttons);
+ add_controls("Global lights", ControlType::LIGHT, SpinnerGroup::GLOBAL_LIGHTS, mapping_proto, global_lights);
fill_controls_from_mapping(mapping_proto);
// Auto-resize every column but the last.
is.spinner->setFocus();
}
}
+ for (const InstantiatedSpinner &is : light_spinners) {
+ if (int(is.bus_idx) == next_bus_idx && is.field_number == focus.field_number) {
+ is.spinner->setFocus();
+ }
+ }
}
void MIDIMappingDialog::ok_clicked()
get_mutable_bus_message<MIDIButtonProto>(mapping_proto.get(), is.bus_idx, is.field_number);
button_proto->set_note_number(val);
}
+ for (const InstantiatedSpinner &is : light_spinners) {
+ const int val = is.spinner->value();
+ if (val == 0) {
+ continue;
+ }
+
+ MIDILightProto *light_proto =
+ get_mutable_bus_message<MIDILightProto>(mapping_proto.get(), is.bus_idx, is.field_number);
+ light_proto->set_note_number(val);
+ }
int highest_bank_used = 0; // 1-indexed.
for (const InstantiatedComboBox &ic : bank_combo_boxes) {
const int val = ic.combo_box->currentIndex();
if (control_type == ControlType::CONTROLLER) {
controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number });
- } else {
- assert(control_type == ControlType::BUTTON);
+ } else if (control_type == ControlType::BUTTON) {
button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number });
+ } else {
+ assert(control_type == ControlType::LIGHT);
+ light_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number });
}
spinners[bus_idx][control.field_number] = SpinnerAndGroup{ spinner, spinner_group };
connect(spinner, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
for (const InstantiatedSpinner &is : button_spinners) {
is.spinner->setValue(get_button_mapping(mapping_proto, is.bus_idx, is.field_number, 0));
}
+ for (const InstantiatedSpinner &is : light_spinners) {
+ is.spinner->setValue(get_light_mapping(mapping_proto, is.bus_idx, is.field_number, 0));
+ }
for (const InstantiatedComboBox &ic : bank_combo_boxes) {
ic.combo_box->setCurrentIndex(get_bank(mapping_proto, ic.field_number, -1) + 1);
}
is.spinner->selectAll();
}
}
+ for (const InstantiatedSpinner &is : light_spinners) {
+ if (is.spinner->hasFocus()) {
+ is.spinner->setValue(note);
+ is.spinner->selectAll();
+ }
+ }
}
pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx, MIDIMappingDialog::SpinnerGroup spinner_group)
ALL_GROUPS = -1,
PER_BUS_CONTROLLERS,
PER_BUS_BUTTONS,
+ PER_BUS_LIGHTS,
GLOBAL_CONTROLLERS,
- GLOBAL_BUTTONS
+ GLOBAL_BUTTONS,
+ GLOBAL_LIGHTS
};
void add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number);
- enum class ControlType { CONTROLLER, BUTTON };
+ enum class ControlType { CONTROLLER, BUTTON, LIGHT };
void add_controls(const std::string &heading, ControlType control_type,
SpinnerGroup spinner_group,
const MIDIMappingProto &mapping_proto, const std::vector<Control> &controls);
};
std::vector<InstantiatedSpinner> controller_spinners;
std::vector<InstantiatedSpinner> button_spinners;
+ std::vector<InstantiatedSpinner> light_spinners;
std::vector<InstantiatedComboBox> bank_combo_boxes;
// Keyed on bus index, then field number.