openMSX
MidiSessionALSA.cc
Go to the documentation of this file.
1 #include "MidiSessionALSA.hh"
2 #include "CliComm.hh"
3 #include "MidiOutDevice.hh"
4 #include "PlugException.hh"
5 #include "PluggingController.hh"
6 #include "serialize.hh"
7 
8 #include <iostream>
9 #include <memory>
10 
11 
12 namespace openmsx {
13 
14 // MidiOutALSA ==============================================================
15 
16 class MidiOutALSA final : public MidiOutDevice {
17 public:
19  snd_seq_t& seq,
20  snd_seq_client_info_t& cinfo, snd_seq_port_info_t& pinfo);
21  ~MidiOutALSA() override;
22  MidiOutALSA(const MidiOutALSA&) = delete;
23  MidiOutALSA& operator=(const MidiOutALSA&) = delete;
24 
25  // Pluggable
26  void plugHelper(Connector& connector, EmuTime::param time) override;
27  void unplugHelper(EmuTime::param time) override;
28  [[nodiscard]] std::string_view getName() const override;
29  [[nodiscard]] std::string_view getDescription() const override;
30 
31  // MidiOutDevice
32  void recvMessage(
33  const std::vector<uint8_t>& message, EmuTime::param time) override;
34 
35  template<typename Archive>
36  void serialize(Archive& ar, unsigned version);
37 
38 private:
39  void connect();
40  void disconnect();
41 
42 private:
43  snd_seq_t& seq;
44  snd_midi_event_t* event_parser;
45  int sourcePort;
46  int destClient;
47  int destPort;
48  std::string name;
49  std::string desc;
50  bool connected;
51 };
52 
54  snd_seq_t& seq_,
55  snd_seq_client_info_t& cinfo, snd_seq_port_info_t& pinfo)
56  : seq(seq_)
57  , sourcePort(-1)
58  , destClient(snd_seq_port_info_get_client(&pinfo))
59  , destPort(snd_seq_port_info_get_port(&pinfo))
60  , name(snd_seq_client_info_get_name(&cinfo))
61  , desc(snd_seq_port_info_get_name(&pinfo))
62  , connected(false)
63 {
64 }
65 
67 {
68  if (connected) {
69  disconnect();
70  }
71 }
72 
73 void MidiOutALSA::plugHelper(Connector& /*connector_*/, EmuTime::param /*time*/)
74 {
75  connect();
76 }
77 
78 void MidiOutALSA::unplugHelper(EmuTime::param /*time*/)
79 {
80  disconnect();
81 }
82 
83 void MidiOutALSA::connect()
84 {
85  sourcePort = snd_seq_create_simple_port(
86  &seq, "MIDI out pluggable",
87  0, SND_SEQ_PORT_TYPE_MIDI_GENERIC);
88  if (sourcePort < 0) {
89  throw PlugException(
90  "Failed to create ALSA port: ", snd_strerror(sourcePort));
91  }
92 
93  int err = snd_seq_connect_to(&seq, sourcePort, destClient, destPort);
94  if (err) {
95  snd_seq_delete_simple_port(&seq, sourcePort);
96  throw PlugException(
97  "Failed to connect to ALSA port "
98  "(", destClient, ':', destPort, ")"
99  ": ", snd_strerror(err));
100  }
101 
102  snd_midi_event_new(MAX_MESSAGE_SIZE, &event_parser);
103 
104  connected = true;
105 }
106 
107 void MidiOutALSA::disconnect()
108 {
109  snd_midi_event_free(event_parser);
110  snd_seq_disconnect_to(&seq, sourcePort, destClient, destPort);
111  snd_seq_delete_simple_port(&seq, sourcePort);
112 
113  connected = false;
114 }
115 
116 std::string_view MidiOutALSA::getName() const
117 {
118  return name;
119 }
120 
121 std::string_view MidiOutALSA::getDescription() const
122 {
123  return desc;
124 }
125 
127  const std::vector<uint8_t>& message, EmuTime::param /*time*/)
128 {
129  snd_seq_event_t ev;
130  snd_seq_ev_clear(&ev);
131 
132  // Set routing.
133  snd_seq_ev_set_source(&ev, sourcePort);
134  snd_seq_ev_set_subs(&ev);
135 
136  // Set message.
137  long encodeLen = snd_midi_event_encode(
138  event_parser, message.data(), message.size(), &ev);
139  if (encodeLen < 0) {
140  std::cerr << "Error encoding MIDI message of type "
141  << std::hex << int(message[0]) << std::dec
142  << ": " << snd_strerror(encodeLen) << '\n';
143  return;
144  }
145  if (ev.type == SND_SEQ_EVENT_NONE) {
146  std::cerr << "Incomplete MIDI message of type "
147  << std::hex << int(message[0]) << std::dec << '\n';
148  return;
149  }
150 
151  // Send event.
152  snd_seq_ev_set_direct(&ev);
153  int err = snd_seq_event_output(&seq, &ev);
154  if (err < 0) {
155  std::cerr << "Error sending MIDI event: "
156  << snd_strerror(err) << '\n';
157  }
158  snd_seq_drain_output(&seq);
159 }
160 
161 template<typename Archive>
162 void MidiOutALSA::serialize(Archive& /*ar*/, unsigned /*version*/)
163 {
164  if constexpr (Archive::IS_LOADER) {
165  connect();
166  }
167 }
170 
171 
172 // MidiSessionALSA ==========================================================
173 
174 std::unique_ptr<MidiSessionALSA> MidiSessionALSA::instance;
175 
177  PluggingController& controller, CliComm& cliComm)
178 {
179  if (!instance) {
180  // Open the sequencer.
181  snd_seq_t* seq;
182  int err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
183  if (err < 0) {
184  cliComm.printError(
185  "Could not open sequencer: ", snd_strerror(err));
186  return;
187  }
188  snd_seq_set_client_name(seq, "openMSX");
189  instance.reset(new MidiSessionALSA(*seq));
190  }
191  instance->scanClients(controller);
192 }
193 
194 MidiSessionALSA::MidiSessionALSA(snd_seq_t& seq_)
195  : seq(seq_)
196 {
197 }
198 
200 {
201  // While the Pluggables still have a copy of this pointer, they won't
202  // be accessing it anymore when openMSX is exiting.
203  snd_seq_close(&seq);
204 }
205 
206 void MidiSessionALSA::scanClients(PluggingController& controller)
207 {
208  // Iterate through all clients.
209  snd_seq_client_info_t* cinfo;
210  snd_seq_client_info_alloca(&cinfo);
211  snd_seq_client_info_set_client(cinfo, -1);
212  while (snd_seq_query_next_client(&seq, cinfo) >= 0) {
213  int client = snd_seq_client_info_get_client(cinfo);
214  if (client == SND_SEQ_CLIENT_SYSTEM) {
215  continue;
216  }
217 
218  // TODO: When there is more than one usable port per client,
219  // register them as separate pluggables.
220  snd_seq_port_info_t* pinfo;
221  snd_seq_port_info_alloca(&pinfo);
222  snd_seq_port_info_set_client(pinfo, client);
223  snd_seq_port_info_set_port(pinfo, -1);
224  while (snd_seq_query_next_port(&seq, pinfo) >= 0) {
225  unsigned int type = snd_seq_port_info_get_type(pinfo);
226  if (!(type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) {
227  continue;
228  }
229  constexpr unsigned int wrcaps =
230  SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
231  if ((snd_seq_port_info_get_capability(pinfo) & wrcaps) == wrcaps) {
232  controller.registerPluggable(std::make_unique<MidiOutALSA>(
233  seq, *cinfo, *pinfo
234  ));
235  }
236  }
237  }
238 }
239 
240 } // namespace openmsx
void printError(std::string_view message)
Definition: CliComm.cc:15
Represents something you can plug devices into.
Definition: Connector.hh:21
void serialize(Archive &ar, unsigned version)
MidiOutALSA & operator=(const MidiOutALSA &)=delete
MidiOutALSA(snd_seq_t &seq, snd_seq_client_info_t &cinfo, snd_seq_port_info_t &pinfo)
MidiOutALSA(const MidiOutALSA &)=delete
void plugHelper(Connector &connector, EmuTime::param time) override
std::string_view getName() const override
Name used to identify this pluggable.
void recvMessage(const std::vector< uint8_t > &message, EmuTime::param time) override
Called when a full MIDI message is ready to be sent.
void unplugHelper(EmuTime::param time) override
std::string_view getDescription() const override
Description for this pluggable.
Pluggable that connects an MSX MIDI out port to a host MIDI device.
static constexpr size_t MAX_MESSAGE_SIZE
The limit for the amount of data we'll put into one MIDI message.
Lists ALSA MIDI ports we can connect to.
static void registerAll(PluggingController &controller, CliComm &cliComm)
Thrown when a plug action fails.
Central administration of Connectors and Pluggables.
void registerPluggable(std::unique_ptr< Pluggable > pluggable)
Add a Pluggable to the registry.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983