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