Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #ifndef MIDIS_SEQ_HANDLER_H_ |
| 6 | #define MIDIS_SEQ_HANDLER_H_ |
| 7 | |
| 8 | #include <memory> |
| 9 | |
Hidehiko Abe | b9eb369 | 2019-09-19 01:15:11 +0900 | [diff] [blame] | 10 | #include <base/files/file_descriptor_watcher_posix.h> |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 11 | #include <base/memory/weak_ptr.h> |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 12 | #include <gtest/gtest_prod.h> |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 13 | |
| 14 | #include "midis/device.h" |
| 15 | #include "midis/device_tracker.h" |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 16 | |
| 17 | namespace midis { |
| 18 | |
| 19 | class DeviceTracker; |
| 20 | |
| 21 | // Class to handle all interactions with the ALSA sequencer interface. |
| 22 | // NOTE: The term "input" refers to data received *from* the MIDI H/W and |
| 23 | // external clients that are registered to the ALSA sequencer interfance. The |
| 24 | // term "output" refers to data that a client of midis write *to* MIDI H/W and |
| 25 | // external clients. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 26 | class SeqHandler { |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 27 | public: |
| 28 | using AddDeviceCallback = base::Callback<void(std::unique_ptr<Device>)>; |
| 29 | using RemoveDeviceCallback = base::Callback<void(uint32_t, uint32_t)>; |
| 30 | using HandleReceiveDataCallback = |
| 31 | base::Callback<void(uint32_t, uint32_t, uint32_t, const char*, size_t)>; |
| 32 | using IsDevicePresentCallback = base::Callback<bool(uint32_t, uint32_t)>; |
| 33 | using IsPortPresentCallback = |
| 34 | base::Callback<bool(uint32_t, uint32_t, uint32_t)>; |
| 35 | |
| 36 | struct SeqDeleter { |
| 37 | void operator()(snd_seq_t* seq) const { snd_seq_close(seq); } |
| 38 | }; |
| 39 | |
| 40 | struct MidiEventDeleter { |
| 41 | void operator()(snd_midi_event_t* coder) const { |
| 42 | snd_midi_event_free(coder); |
| 43 | } |
| 44 | }; |
| 45 | |
| 46 | using ScopedMidiEventPtr = |
| 47 | std::unique_ptr<snd_midi_event_t, MidiEventDeleter>; |
| 48 | using ScopedSeqPtr = std::unique_ptr<snd_seq_t, SeqDeleter>; |
| 49 | |
| 50 | SeqHandler(AddDeviceCallback add_device_cb, |
| 51 | RemoveDeviceCallback remove_device_cb, |
| 52 | HandleReceiveDataCallback handle_rx_data_cb, |
| 53 | IsDevicePresentCallback is_device_present_cb, |
| 54 | IsPortPresentCallback is_port_present_cb); |
| 55 | |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 56 | virtual ~SeqHandler() = default; |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 57 | |
| 58 | // Initializes the ALSA seq interface. Creates client handles for input and |
| 59 | // output, as well create an input port to receive messages (announce as |
| 60 | // well as MIDI data) from ALSA seq. Also starts off the file watcher which |
| 61 | // watches for events on the input port. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 62 | bool InitSeq(); |
| 63 | void ProcessAlsaClientFd(); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 64 | |
| 65 | // Creates a Device object and runs the necessary callback to register that |
| 66 | // object with DeviceTracker, stored in |add_device_cb_| |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 67 | virtual void AddSeqDevice(uint32_t device_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 68 | |
| 69 | // At present, we don't support hotplugging of individual ports in devices. |
| 70 | // so, we enumerate all the available ports in AddAlsaDevice(). |
| 71 | // This function is here merely to handle MIDI events associated with any |
| 72 | // port being added or removed later (and to print an error message, since |
| 73 | // we don't support it yet) |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 74 | virtual void AddSeqPort(uint32_t device_id, uint32_t port_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 75 | |
| 76 | // Runs the relevant callback, stored in |remove_device_cb_|, when a MIDI H/W |
| 77 | // device or external client is removed from the ALSA sequencer interface. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 78 | virtual void RemoveSeqDevice(uint32_t device_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 79 | |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 80 | virtual void RemoveSeqPort(uint32_t device_id, uint32_t port_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 81 | |
| 82 | // Callback to run when starting an input port (establishes a subscription, |
| 83 | // and creates a relevant port on the server side, in necessary). |
| 84 | // Returns true on success, false otherwise. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 85 | bool SubscribeInPort(uint32_t device_id, uint32_t port_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 86 | |
| 87 | // Callback to run when starting an input port (establishes a subscription, |
| 88 | // and creates a relevant port on the server side, in necessary). |
| 89 | // Returns created seq port id success, -1 otherwise. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 90 | int SubscribeOutPort(uint32_t device_id, uint32_t port_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 91 | |
| 92 | // The following two functions undo the work of the Subscribe*Port() function, |
| 93 | // for input and output ports respectively. |
Prashant Malani | e8f0c67 | 2018-03-16 17:03:58 -0700 | [diff] [blame] | 94 | void UnsubscribeInPort(uint32_t device_id, uint32_t port_id); |
| 95 | void UnsubscribeOutPort(int out_port_id); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 96 | |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 97 | // Encodes the bytes in a MIDI buffer into the provided |encoder|. |
| 98 | bool EncodeMidiBytes(int out_port_id, |
| 99 | snd_seq_t* out_client, |
| 100 | const uint8_t* buffer, |
| 101 | size_t buffer_len, |
| 102 | snd_midi_event_t* encoder); |
| 103 | |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 104 | // Callback to send MIDI data to the H/W. This callback is generally called by |
| 105 | // a Device handler which receives MIDI data from a client (e.g ARC++). The |
| 106 | // Device handler will in turn be called by a Client handler which is |
| 107 | // listening for data from it's client. |
Tom Hughes | aebcdce | 2020-08-27 15:18:39 -0700 | [diff] [blame] | 108 | void SendMidiData(int out_port_id, const uint8_t* buffer, size_t buf_len); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 109 | |
| 110 | // This function processes the MIDI data received from H/W or an external |
| 111 | // client, and invokes the callback |handle_rx_data_cb_| which handles the |
| 112 | // data accordingly. |
Ben Chan | ba3382c | 2019-09-19 14:08:30 -0700 | [diff] [blame] | 113 | virtual void ProcessMidiEvent(snd_seq_event_t* event); |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 114 | |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 115 | // Wrappers for functions that interact with the ALSA Sequencer interface. |
| 116 | // These are kept separately, because the intention is to mock these functions |
| 117 | // in unit tests. |
| 118 | virtual int SndSeqEventOutputDirect(snd_seq_t* out_client, |
| 119 | snd_seq_event_t* event); |
Prashant Malani | a9bbf54 | 2017-10-19 18:27:18 -0700 | [diff] [blame] | 120 | virtual int SndSeqEventInput(snd_seq_t* in_client, snd_seq_event_t** ev); |
| 121 | virtual int SndSeqEventInputPending(snd_seq_t* in_client, |
| 122 | int fetch_sequencer); |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 123 | |
| 124 | protected: |
| 125 | // For testing purposes. |
| 126 | SeqHandler(); |
| 127 | |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 128 | private: |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 129 | friend class SeqHandlerTest; |
Prashant Malani | 519dd74 | 2018-03-15 22:01:50 -0700 | [diff] [blame] | 130 | friend class SeqHandlerFuzzer; |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 131 | FRIEND_TEST(SeqHandlerTest, TestEncodeBytes); |
Prashant Malani | a9bbf54 | 2017-10-19 18:27:18 -0700 | [diff] [blame] | 132 | FRIEND_TEST(SeqHandlerTest, TestProcessAlsaClientFdPositive); |
Prashant Malani | 78b59c3 | 2017-10-24 22:54:51 -0700 | [diff] [blame] | 133 | FRIEND_TEST(SeqHandlerTest, TestProcessMidiEventsPositive); |
| 134 | FRIEND_TEST(SeqHandlerTest, TestProcessMidiEventsNegative); |
Prashant Malani | 3c54036 | 2017-09-28 13:35:14 -0700 | [diff] [blame] | 135 | |
Prashant Malani | 8ca4ab3 | 2017-10-03 15:12:49 -0700 | [diff] [blame] | 136 | // Enumerates all clients which are already connected to the ALSA Sequencer. |
| 137 | void EnumerateExistingDevices(); |
| 138 | |
Prashant Malani | 78b59c3 | 2017-10-24 22:54:51 -0700 | [diff] [blame] | 139 | // Creates a MIDI event wrapped in a ScopedMidiEventPtr. |
| 140 | static ScopedMidiEventPtr CreateMidiEvent(size_t buf_size); |
| 141 | |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 142 | std::unique_ptr<snd_seq_t, SeqDeleter> in_client_; |
| 143 | std::unique_ptr<snd_seq_t, SeqDeleter> out_client_; |
| 144 | std::unique_ptr<snd_midi_event_t, MidiEventDeleter> decoder_; |
| 145 | int in_client_id_; |
| 146 | int out_client_id_; |
| 147 | int in_port_id_; |
| 148 | std::unique_ptr<pollfd> pfd_; |
Hidehiko Abe | b9eb369 | 2019-09-19 01:15:11 +0900 | [diff] [blame] | 149 | std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher_; |
Prashant Malani | fd1e200 | 2017-08-09 13:22:59 -0700 | [diff] [blame] | 150 | |
| 151 | AddDeviceCallback add_device_cb_; |
| 152 | RemoveDeviceCallback remove_device_cb_; |
| 153 | HandleReceiveDataCallback handle_rx_data_cb_; |
| 154 | IsDevicePresentCallback is_device_present_cb_; |
| 155 | IsPortPresentCallback is_port_present_cb_; |
| 156 | base::WeakPtrFactory<SeqHandler> weak_factory_; |
| 157 | |
| 158 | DISALLOW_COPY_AND_ASSIGN(SeqHandler); |
| 159 | }; |
| 160 | |
| 161 | } // namespace midis |
| 162 | |
| 163 | #endif // MIDIS_SEQ_HANDLER_H_ |