+
+void MIDIMapper::refresh_highlights()
+{
+ receiver->clear_all_highlights();
+ update_highlights();
+}
+
+void MIDIMapper::refresh_lights()
+{
+ lock_guard<mutex> lock(mu);
+ update_lights_lock_held();
+}
+
+void MIDIMapper::update_highlights()
+{
+ if (num_subscribed_ports.load() == 0) {
+ receiver->clear_all_highlights();
+ return;
+ }
+
+ // Global controllers.
+ bool highlight_locut = false;
+ bool highlight_limiter_threshold = false;
+ bool highlight_makeup_gain = false;
+ bool highlight_toggle_limiter = false;
+ bool highlight_toggle_auto_makeup_gain = false;
+ for (size_t bus_idx = 0; bus_idx < size_t(mapping_proto->bus_mapping_size()); ++bus_idx) {
+ if (has_active_controller(
+ bus_idx, MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber)) {
+ highlight_locut = true;
+ }
+ if (has_active_controller(
+ bus_idx, MIDIMappingBusProto::kLimiterThresholdFieldNumber, MIDIMappingProto::kLimiterThresholdBankFieldNumber)) {
+ highlight_limiter_threshold = true;
+ }
+ if (has_active_controller(
+ bus_idx, MIDIMappingBusProto::kMakeupGainFieldNumber, MIDIMappingProto::kMakeupGainBankFieldNumber)) {
+ highlight_makeup_gain = true;
+ }
+ if (has_active_controller(
+ bus_idx, MIDIMappingBusProto::kToggleLimiterFieldNumber, MIDIMappingProto::kToggleLimiterBankFieldNumber)) {
+ highlight_toggle_limiter = true;
+ }
+ if (has_active_controller(
+ bus_idx, MIDIMappingBusProto::kToggleAutoMakeupGainFieldNumber, MIDIMappingProto::kToggleAutoMakeupGainBankFieldNumber)) {
+ highlight_toggle_auto_makeup_gain = true;
+ }
+ }
+ receiver->highlight_locut(highlight_locut);
+ receiver->highlight_limiter_threshold(highlight_limiter_threshold);
+ receiver->highlight_makeup_gain(highlight_makeup_gain);
+ receiver->highlight_toggle_limiter(highlight_toggle_limiter);
+ receiver->highlight_toggle_auto_makeup_gain(highlight_toggle_auto_makeup_gain);
+
+ // Per-bus controllers.
+ for (size_t bus_idx = 0; bus_idx < size_t(mapping_proto->bus_mapping_size()); ++bus_idx) {
+ receiver->highlight_stereo_width(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kStereoWidthFieldNumber, MIDIMappingProto::kStereoWidthBankFieldNumber));
+ receiver->highlight_treble(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kTrebleFieldNumber, MIDIMappingProto::kTrebleBankFieldNumber));
+ receiver->highlight_mid(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kMidFieldNumber, MIDIMappingProto::kMidBankFieldNumber));
+ receiver->highlight_bass(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kBassFieldNumber, MIDIMappingProto::kBassBankFieldNumber));
+ receiver->highlight_gain(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kGainFieldNumber, MIDIMappingProto::kGainBankFieldNumber));
+ receiver->highlight_compressor_threshold(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kCompressorThresholdFieldNumber, MIDIMappingProto::kCompressorThresholdBankFieldNumber));
+ receiver->highlight_fader(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kFaderFieldNumber, MIDIMappingProto::kFaderBankFieldNumber));
+ receiver->highlight_mute(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kToggleMuteFieldNumber, MIDIMappingProto::kToggleMuteBankFieldNumber));
+ receiver->highlight_toggle_locut(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kToggleLocutFieldNumber, MIDIMappingProto::kToggleLocutBankFieldNumber));
+ receiver->highlight_toggle_auto_gain_staging(bus_idx, has_active_controller(
+ bus_idx, MIDIMappingBusProto::kToggleAutoGainStagingFieldNumber, MIDIMappingProto::kToggleAutoGainStagingBankFieldNumber));
+ receiver->highlight_toggle_compressor(bus_idx, has_active_controller(
+ 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_mute(bus_idx)) {
+ activate_lights(bus_idx, MIDIMappingBusProto::kIsMutedFieldNumber, &active_lights);
+ }
+ 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());
+ }
+}