1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
media / midi / midi_manager.h [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_MIDI_MIDI_MANAGER_H_
#define MEDIA_MIDI_MIDI_MANAGER_H_
#include <stddef.h>
#include <stdint.h>
#include <set>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "media/midi/midi_export.h"
#include "media/midi/midi_service.mojom.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace midi {
class MidiService;
// A MidiManagerClient registers with the MidiManager to receive MIDI data.
// See MidiManager::RequestAccess() and MidiManager::ReleaseAccess()
// for details.
// TODO(toyoshim): Consider to have a MidiServiceClient interface.
class MIDI_EXPORT MidiManagerClient {
public:
virtual ~MidiManagerClient() {}
// AddInputPort() and AddOutputPort() are called before CompleteStartSession()
// is called to notify existing MIDI ports, and also called after that to
// notify new MIDI ports are added.
virtual void AddInputPort(const mojom::PortInfo& info) = 0;
virtual void AddOutputPort(const mojom::PortInfo& info) = 0;
// SetInputPortState() and SetOutputPortState() are called to notify a known
// device gets disconnected, or connected again.
virtual void SetInputPortState(uint32_t port_index,
mojom::PortState state) = 0;
virtual void SetOutputPortState(uint32_t port_index,
mojom::PortState state) = 0;
// CompleteStartSession() is called when platform dependent preparation is
// finished.
virtual void CompleteStartSession(mojom::Result result) = 0;
// ReceiveMidiData() is called when MIDI data has been received from the
// MIDI system.
// |port_index| represents the specific input port from input_ports().
// |data| represents a series of bytes encoding one or more MIDI messages.
// |length| is the number of bytes in |data|.
// |timestamp| is the time the data was received, in seconds.
virtual void ReceiveMidiData(uint32_t port_index,
const uint8_t* data,
size_t length,
base::TimeTicks timestamp) = 0;
// AccumulateMidiBytesSent() is called to acknowledge when bytes have
// successfully been sent to the hardware.
// This happens as a result of the client having previously called
// MidiManager::DispatchSendMidiData().
virtual void AccumulateMidiBytesSent(size_t n) = 0;
// Detach() is called when MidiManager is going to shutdown immediately.
// Client should not touch MidiManager instance after Detach() is called.
virtual void Detach() = 0;
};
// Manages access to all MIDI hardware. MidiManager runs on the I/O thread.
//
// Note: We will eventually remove utility functions that are shared among
// platform dependent MidiManager inheritances such as MidiManagerClient
// management. MidiService should provide such shareable utility functions as
// it does TaskService.
class MIDI_EXPORT MidiManager {
public:
static const size_t kMaxPendingClientCount = 128;
explicit MidiManager(MidiService* service);
MidiManager(const MidiManager&) = delete;
MidiManager& operator=(const MidiManager&) = delete;
virtual ~MidiManager();
static MidiManager* Create(MidiService* service);
// A client calls StartSession() to receive and send MIDI data.
// If the session is ready to start, the MIDI system is lazily initialized
// and the client is registered to receive MIDI data.
// CompleteStartSession() is called with mojom::Result::OK if the session is
// started. Otherwise CompleteStartSession() is called with a proper
// mojom::Result code.
void StartSession(MidiManagerClient* client);
// A client calls EndSession() to stop receiving MIDI data.
// Returns false if |client| did not start a session.
bool EndSession(MidiManagerClient* client);
// Returns true if there is at least one client that keep a session open.
bool HasOpenSession();
// DispatchSendMidiData() is called when MIDI data should be sent to the MIDI
// system.
// This method is supposed to return immediately and should not block.
// |port_index| represents the specific output port from output_ports().
// |data| represents a series of bytes encoding one or more MIDI messages.
// |length| is the number of bytes in |data|.
// |timestamp| is the time to send the data. A value of 0 means send "now" or
// as soon as possible. The default implementation is for unsupported
// platforms.
virtual void DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
base::TimeTicks timestamp);
// This method ends all sessions by detaching and removing all registered
// clients. This method can be called from any thread.
void EndAllSessions();
protected:
friend class MidiManagerUsb;
// Initializes the platform dependent MIDI system. MidiManager class has a
// default implementation that synchronously calls CompleteInitialization()
// with mojom::Result::NOT_SUPPORTED. A derived class for a specific platform
// should override this method correctly.
// Platform dependent initialization can be processed synchronously or
// asynchronously. When the initialization is completed,
// CompleteInitialization() should be called with |result|.
// |result| should be mojom::Result::OK on success, otherwise a proper
// mojom::Result.
virtual void StartInitialization();
// Called from a platform dependent implementation of StartInitialization().
// The method distributes |result| to MIDIManagerClient objects in
// |pending_clients_|.
void CompleteInitialization(mojom::Result result);
// The following five methods can be called on any thread to notify clients of
// status changes on ports, or to obtain port status.
void AddInputPort(const mojom::PortInfo& info);
void AddOutputPort(const mojom::PortInfo& info);
void SetInputPortState(uint32_t port_index, mojom::PortState state);
void SetOutputPortState(uint32_t port_index, mojom::PortState state);
mojom::PortState GetOutputPortState(uint32_t port_index);
// Invoke AccumulateMidiBytesSent() for |client| safely. If the session was
// already closed, do nothing. Can be called on any thread.
void AccumulateMidiBytesSent(MidiManagerClient* client, size_t n);
// Dispatches to all clients. Can be called on any thread.
void ReceiveMidiData(uint32_t port_index,
const uint8_t* data,
size_t length,
base::TimeTicks time);
// Only for testing.
size_t GetClientCountForTesting();
size_t GetPendingClientCountForTesting();
MidiService* service() { return service_; }
private:
enum class InitializationState {
NOT_STARTED,
STARTED,
COMPLETED,
};
// Note: Members that are not protected by any lock should be accessed only on
// the I/O thread.
// Tracks platform dependent initialization state.
InitializationState initialization_state_ = InitializationState::NOT_STARTED;
// Keeps the platform dependent initialization result if initialization is
// completed. Otherwise keeps mojom::Result::NOT_INITIALIZED.
mojom::Result result_ = mojom::Result::NOT_INITIALIZED;
// Keeps track of all clients who are waiting for CompleteStartSession().
std::set<raw_ptr<MidiManagerClient, SetExperimental>> pending_clients_
GUARDED_BY(lock_);
// Keeps track of all clients who wish to receive MIDI data.
std::set<raw_ptr<MidiManagerClient, SetExperimental>> clients_
GUARDED_BY(lock_);
// Keeps a SingleThreadTaskRunner of the thread that calls StartSession in
// order to invoke CompleteStartSession() on the thread. This is touched only
// on the IO thread usually, but to be guarded by |lock_| for thread checks.
scoped_refptr<base::SingleThreadTaskRunner> session_thread_runner_
GUARDED_BY(lock_);
// Keeps all PortInfo.
std::vector<mojom::PortInfo> input_ports_ GUARDED_BY(lock_);
std::vector<mojom::PortInfo> output_ports_ GUARDED_BY(lock_);
// Tracks if actual data transmission happens.
bool data_sent_ GUARDED_BY(lock_) = false;
bool data_received_ GUARDED_BY(lock_) = false;
// Protects members above.
base::Lock lock_;
// MidiService outlives MidiManager.
const raw_ptr<MidiService> service_;
};
} // namespace midi
#endif // MEDIA_MIDI_MIDI_MANAGER_H_