From: Steinar H. Gunderson Date: Thu, 17 Jan 2019 21:18:13 +0000 (+0100) Subject: Add infrastructure for MIDI lights that are controllers instead of notes. X-Git-Tag: 1.8.2~9 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=f1a8ab7eb34a89a104093c3931b1d51fcb2684e6 Add infrastructure for MIDI lights that are controllers instead of notes. --- diff --git a/futatabi/midi_mapper.cpp b/futatabi/midi_mapper.cpp index 36a2e43..93e367a 100644 --- a/futatabi/midi_mapper.cpp +++ b/futatabi/midi_mapper.cpp @@ -206,7 +206,7 @@ void MIDIMapper::refresh_lights() void MIDIMapper::update_lights_lock_held() { - map active_lights; // Desired state. + map active_lights; // Desired state. if (current_controller_bank == 0) { activate_mapped_light(*mapping_proto, MIDIMappingProto::kBank1IsSelectedFieldNumber, &active_lights); } diff --git a/nageru/midi_mapper.cpp b/nageru/midi_mapper.cpp index 1fdb72e..d0a42ec 100644 --- a/nageru/midi_mapper.cpp +++ b/nageru/midi_mapper.cpp @@ -318,7 +318,7 @@ void MIDIMapper::update_lights_lock_held() return; } - map active_lights; // Desired state. + map active_lights; // Desired state. if (current_controller_bank == 0) { activate_lights_all_buses(MIDIMappingBusProto::kBank1IsSelectedFieldNumber, &active_lights); } @@ -363,7 +363,7 @@ void MIDIMapper::update_lights_lock_held() midi_device.update_lights(active_lights); } -void MIDIMapper::activate_lights_all_buses(int field_number, map *active_lights) +void MIDIMapper::activate_lights_all_buses(int field_number, map *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); diff --git a/nageru/midi_mapper.h b/nageru/midi_mapper.h index 3ecdee2..9630780 100644 --- a/nageru/midi_mapper.h +++ b/nageru/midi_mapper.h @@ -109,7 +109,7 @@ private: void update_highlights(); void update_lights_lock_held(); - void activate_lights_all_buses(int field_number, std::map *active_lights); + void activate_lights_all_buses(int field_number, std::map *active_lights); std::atomic should_quit{false}; int should_quit_fd; diff --git a/shared/midi_device.cpp b/shared/midi_device.cpp index a232961..e4b1a11 100644 --- a/shared/midi_device.cpp +++ b/shared/midi_device.cpp @@ -233,42 +233,63 @@ void MIDIDevice::subscribe_to_port_lock_held(snd_seq_t *seq, const snd_seq_addr_ } // The current status of the device is unknown, so refresh it. - map active_lights = move(current_light_status); + map active_lights = move(current_light_status); current_light_status.clear(); update_lights_lock_held(active_lights); } -void MIDIDevice::update_lights_lock_held(const map &active_lights) +void MIDIDevice::update_lights_lock_held(const map &active_lights) { if (alsa_seq == nullptr) { return; } unsigned num_events = 0; - for (unsigned note_num = 1; note_num <= 127; ++note_num) { // Note: Pitch bend is ignored. - const auto it = active_lights.find(note_num); - uint8_t velocity = (it == active_lights.end()) ? 0 : it->second; - if (current_light_status.count(note_num) && - current_light_status[note_num] == velocity) { - // Already known to be in the desired state. - continue; - } + for (auto type : { LightKey::NOTE, LightKey::CONTROLLER }) { + for (unsigned num = 1; num <= 127; ++num) { // Note: Pitch bend is ignored. + LightKey key{type, num}; + const auto it = active_lights.find(key); + uint8_t value; // Velocity for notes, controller value for controllers. + + // Notes have a natural “off”, while controllers don't really. + // For some reason, not all devices respond to note off. + // Use note-on with value of 0 (which is equivalent) instead. + if (it == active_lights.end()) { + // Notes have a natural “off”, while controllers don't really, + // so just skip them if we have no set value. + if (type == LightKey::CONTROLLER) continue; + + // For some reason, not all devices respond to note off. + // Use note-on with value of 0 (which is equivalent) instead. + value = 0; + } else { + value = it->second; + } + if (current_light_status.count(key) && + current_light_status[key] == value) { + // 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, velocity); - WARN_ON_ERROR("snd_seq_event_output", snd_seq_event_output(alsa_seq, &ev)); - current_light_status[note_num] = velocity; + 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); + + if (type == LightKey::NOTE) { + snd_seq_ev_set_noteon(&ev, /*channel=*/0, num, value); + current_light_status[key] = value; + } else { + snd_seq_ev_set_controller(&ev, /*channel=*/0, num, value); + current_light_status[key] = value; + } + WARN_ON_ERROR("snd_seq_event_output", snd_seq_event_output(alsa_seq, &ev)); + } } WARN_ON_ERROR("snd_seq_drain_output", snd_seq_drain_output(alsa_seq)); } diff --git a/shared/midi_device.h b/shared/midi_device.h index 9c76d18..a9f33d6 100644 --- a/shared/midi_device.h +++ b/shared/midi_device.h @@ -28,11 +28,24 @@ public: class MIDIDevice { public: + struct LightKey { + enum { NOTE, CONTROLLER } type; + unsigned number; + + bool operator< (const LightKey& other) const + { + if (type != other.type) { + return type < other.type; + } + return number < other.number; + } + }; + MIDIDevice(MIDIReceiver *receiver); ~MIDIDevice(); void start_thread(); - void update_lights(const std::map &active_lights) + void update_lights(const std::map &active_lights) { std::lock_guard lock(mu); update_lights_lock_held(active_lights); @@ -42,7 +55,7 @@ private: void thread_func(); void handle_event(snd_seq_t *seq, snd_seq_event_t *event); void subscribe_to_port_lock_held(snd_seq_t *seq, const snd_seq_addr_t &addr); - void update_lights_lock_held(const std::map &active_lights); + void update_lights_lock_held(const std::map &active_lights); std::atomic should_quit{false}; int should_quit_fd; @@ -51,7 +64,7 @@ private: MIDIReceiver *receiver; // Under . std::thread midi_thread; - std::map current_light_status; // Keyed by note number. Under . + std::map current_light_status; // Keyed by note number. Under . snd_seq_t *alsa_seq{nullptr}; // Under . int alsa_queue_id{-1}; // Under . std::atomic num_subscribed_ports{0}; diff --git a/shared/midi_mapper_util.h b/shared/midi_mapper_util.h index 07f2a53..9120fc8 100644 --- a/shared/midi_mapper_util.h +++ b/shared/midi_mapper_util.h @@ -49,7 +49,7 @@ inline bool match_bank_helper(const Proto &msg, int bank_field_number, int bank) // Find what MIDI note the given light (as given by field_number) is mapped to, and enable it. template -void activate_mapped_light(const Proto &msg, int field_number, std::map *active_lights) +void activate_mapped_light(const Proto &msg, int field_number, std::map *active_lights) { using namespace google::protobuf; const FieldDescriptor *descriptor = msg.GetDescriptor()->FindFieldByNumber(field_number); @@ -59,7 +59,8 @@ void activate_mapped_light(const Proto &msg, int field_number, std::map(reflection->GetMessage(msg, descriptor)); - active_lights->emplace(light_proto.note_number(), light_proto.velocity()); + active_lights->emplace(MIDIDevice::LightKey{MIDIDevice::LightKey::NOTE, unsigned(light_proto.note_number())}, + light_proto.velocity()); } inline double map_controller_to_float(int controller, int val)