X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=midi_mapper.cpp;h=fc3b4ef30bf06b274661ad6aa9415d4d0dbb4ff0;hb=9e47a2f661b9d292598ef0277e507458e3dad62f;hp=392fac4b82b89b7570fa736f4b5566bff935752d;hpb=cf729d2ddd503b90413987bf94a928ae31efe0d0;p=nageru diff --git a/midi_mapper.cpp b/midi_mapper.cpp index 392fac4..fc3b4ef 100644 --- a/midi_mapper.cpp +++ b/midi_mapper.cpp @@ -1,16 +1,26 @@ #include "midi_mapper.h" -#include "midi_mapping.pb.h" #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include - +#include +#include #include #include +#include "audio_mixer.h" +#include "midi_mapping.pb.h" + using namespace google::protobuf; using namespace std; using namespace std::placeholders; @@ -42,7 +52,10 @@ MIDIMapper::~MIDIMapper() { should_quit = true; const uint64_t one = 1; - write(should_quit_fd, &one, sizeof(one)); + if (write(should_quit_fd, &one, sizeof(one)) != sizeof(one)) { + perror("write(should_quit_fd)"); + exit(1); + } midi_thread.join(); close(should_quit_fd); } @@ -86,7 +99,7 @@ bool save_midi_mapping_to_file(const MIDIMappingProto &mapping_proto, const stri void MIDIMapper::set_midi_mapping(const MIDIMappingProto &new_mapping) { - lock_guard lock(mapping_mu); + lock_guard lock(mu); if (mapping_proto) { mapping_proto->CopyFrom(new_mapping); } else { @@ -95,6 +108,9 @@ void MIDIMapper::set_midi_mapping(const MIDIMappingProto &new_mapping) num_controller_banks = min(max(mapping_proto->num_controller_banks(), 1), 5); current_controller_bank = 0; + + receiver->clear_all_highlights(); + update_highlights(); } void MIDIMapper::start_thread() @@ -104,10 +120,17 @@ void MIDIMapper::start_thread() const MIDIMappingProto &MIDIMapper::get_current_mapping() const { - lock_guard lock(mapping_mu); + lock_guard lock(mu); return *mapping_proto; } +ControllerReceiver *MIDIMapper::set_receiver(ControllerReceiver *new_receiver) +{ + lock_guard lock(mu); + swap(receiver, new_receiver); + return new_receiver; // Now old receiver. +} + #define RETURN_ON_ERROR(msg, expr) do { \ int err = (expr); \ if (err < 0) { \ @@ -116,9 +139,18 @@ const MIDIMappingProto &MIDIMapper::get_current_mapping() const } \ } 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() { + pthread_setname_np(pthread_self(), "MIDIMapper"); + snd_seq_t *seq; int err; @@ -127,10 +159,23 @@ 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 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)); @@ -151,7 +196,8 @@ void MIDIMapper::thread_func() 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 lock(mu); + subscribe_to_port_lock_held(seq, *snd_seq_port_info_get_addr(pinfo)); } } } @@ -186,6 +232,10 @@ void MIDIMapper::thread_func() if (err < 0) { if (err == -EINTR) continue; if (err == -EAGAIN) break; + if (err == -ENOSPC) { + fprintf(stderr, "snd_seq_event_input: Some events were lost.\n"); + continue; + } fprintf(stderr, "snd_seq_event_input: %s\n", snd_strerror(err)); return; } @@ -198,14 +248,19 @@ void MIDIMapper::thread_func() void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event) { - lock_guard lock(mapping_mu); + if (event->source.client == snd_seq_client_id(seq)) { + // Ignore events we sent out ourselves. + return; + } + + lock_guard 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); - const int controller = event->data.control.param; const float value = map_controller_to_float(event->data.control.value); + receiver->controller_changed(controller); + // Global controllers. match_controller(controller, MIDIMappingBusProto::kLocutFieldNumber, MIDIMappingProto::kLocutBankFieldNumber, value, bind(&ControllerReceiver::set_locut, receiver, _2)); @@ -232,42 +287,55 @@ void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event) case SND_SEQ_EVENT_NOTEON: { const int note = event->data.note.note; - printf("Note: %d\n", note); + receiver->note_on(note); - // Bank change commands. TODO: Highlight the bank change in the UI. 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); if (bus_mapping.has_prev_bank() && 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(); } } @@ -279,30 +347,52 @@ void MIDIMapper::handle_event(snd_seq_t *seq, snd_seq_event_t *event) bind(&ControllerReceiver::toggle_compressor, receiver, _1)); match_button(note, MIDIMappingBusProto::kClearPeakFieldNumber, MIDIMappingProto::kClearPeakBankFieldNumber, bind(&ControllerReceiver::clear_peak, receiver, _1)); + match_button(note, MIDIMappingBusProto::kToggleMuteFieldNumber, MIDIMappingProto::kClearPeakBankFieldNumber, + bind(&ControllerReceiver::toggle_mute, receiver, _1)); + match_button(note, MIDIMappingBusProto::kToggleLimiterFieldNumber, MIDIMappingProto::kToggleLimiterBankFieldNumber, + bind(&ControllerReceiver::toggle_limiter, receiver)); + match_button(note, MIDIMappingBusProto::kToggleAutoMakeupGainFieldNumber, MIDIMappingProto::kToggleAutoMakeupGainBankFieldNumber, + bind(&ControllerReceiver::toggle_auto_makeup_gain, receiver)); + break; } 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); break; + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + if (event->data.connect.sender.client != 0 && // Ignore system senders. + event->data.connect.sender.client != snd_seq_client_id(seq) && + event->data.connect.dest.client == snd_seq_client_id(seq)) { + ++num_subscribed_ports; + update_highlights(); + } + break; + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + if (event->data.connect.sender.client != 0 && // Ignore system senders. + event->data.connect.sender.client != snd_seq_client_id(seq) && + event->data.connect.dest.client == snd_seq_client_id(seq)) { + --num_subscribed_ports; + update_highlights(); + } + break; case SND_SEQ_EVENT_NOTEOFF: case SND_SEQ_EVENT_CLIENT_START: case SND_SEQ_EVENT_CLIENT_EXIT: case SND_SEQ_EVENT_CLIENT_CHANGE: case SND_SEQ_EVENT_PORT_CHANGE: - case SND_SEQ_EVENT_PORT_SUBSCRIBED: - case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: break; default: printf("Ignoring MIDI event of unknown type %d.\n", event->type); } } -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) { + // Client 0 (SNDRV_SEQ_CLIENT_SYSTEM) is basically the system; ignore it. + // MIDI through (SNDRV_SEQ_CLIENT_DUMMY) echoes back what we give it, so ignore that, too. + if (addr.client == 0 || addr.client == 14) { return; } @@ -315,6 +405,18 @@ void MIDIMapper::subscribe_to_port(snd_seq_t *seq, const snd_seq_addr_t &addr) } 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 func) @@ -361,6 +463,18 @@ void MIDIMapper::match_button(int note, int field_number, int bank_field_number, } } +bool MIDIMapper::has_active_controller(unsigned bus_idx, int field_number, int bank_field_number) +{ + if (bank_mismatch(bank_field_number)) { + return false; + } + + 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(); + return bus_reflection->HasField(bus_mapping, descriptor); +} + bool MIDIMapper::bank_mismatch(int bank_field_number) { const FieldDescriptor *bank_descriptor = mapping_proto->GetDescriptor()->FindFieldByNumber(bank_field_number); @@ -368,3 +482,186 @@ bool MIDIMapper::bank_mismatch(int bank_field_number) return (reflection->HasField(*mapping_proto, bank_descriptor) && reflection->GetInt32(*mapping_proto, bank_descriptor) != current_controller_bank); } + +void MIDIMapper::refresh_highlights() +{ + receiver->clear_all_highlights(); + update_highlights(); +} + +void MIDIMapper::refresh_lights() +{ + lock_guard 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_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 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(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 *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(bus_reflection->GetMessage(bus_mapping, descriptor)); + active_lights->insert(light_proto.note_number()); +} + +void MIDIMapper::activate_lights_all_buses(int field_number, set *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(bus_reflection->GetMessage(bus_mapping, descriptor)); + active_lights->insert(light_proto.note_number()); + } +}