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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.midi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* This class is used for sending and receiving data to and from an MIDI device
* Instances of this class are created by {@link MidiManager#openDevice}.
* This class can also be used to provide the implementation for a virtual device.
*
* This class implements Parcelable so it can be returned from MidiService when creating
* virtual MIDI devices.
*
* @hide
*/
public final class MidiDevice implements Parcelable {
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
private ParcelFileDescriptor mParcelFileDescriptor;
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
// lazily populated lists of ports
private final MidiInputPort[] mInputPorts;
private final MidiOutputPort[] mOutputPorts;
// array of receiver lists, indexed by port number
private final ArrayList<MidiReceiver>[] mReceivers;
private int mReceiverCount; // total number of receivers for all ports
/**
* Minimum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
public static final int MIN_PACKED_MESSAGE_SIZE = 10;
/**
* Maximum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
public static final int MAX_PACKED_MESSAGE_SIZE = 12;
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
private final Thread mThread = new Thread() {
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
// read next event
int count = mInputStream.read(buffer);
if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
Log.e(TAG, "Number of bytes read out of range: " + count);
break;
}
int offset = getMessageOffset(buffer, count);
int size = getMessageSize(buffer, count);
long timestamp = getMessageTimeStamp(buffer, count);
int port = getMessagePortNumber(buffer, count);
synchronized (mReceivers) {
ArrayList<MidiReceiver> receivers = mReceivers[port];
if (receivers != null) {
for (int i = 0; i < receivers.size(); i++) {
MidiReceiver receiver = receivers.get(i);
try {
receivers.get(i).onPost(buffer, offset, size, timestamp);
} catch (IOException e) {
Log.e(TAG, "post failed");
deadReceivers.add(receiver);
}
}
// remove any receivers that failed
if (deadReceivers.size() > 0) {
for (MidiReceiver receiver: deadReceivers) {
receivers.remove(receiver);
mReceiverCount--;
}
deadReceivers.clear();
}
if (receivers.size() == 0) {
mReceivers[port] = null;
}
// exit if we have no receivers left
if (mReceiverCount == 0) {
break;
}
}
}
}
} catch (IOException e) {
Log.e(TAG, "read failed");
}
}
};
/**
* MidiDevice should only be instantiated by MidiManager or MidiService
* @hide
*/
public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
mDeviceInfo = deviceInfo;
mParcelFileDescriptor = pfd;
int inputPorts = deviceInfo.getInputPortCount();
int outputPorts = deviceInfo.getOutputPortCount();
mInputPorts = new MidiInputPort[inputPorts];
mOutputPorts = new MidiOutputPort[outputPorts];
mReceivers = new ArrayList[outputPorts];
}
/**
* Called to open a {@link MidiInputPort} for the specified port number.
*
* @param portNumber the number of the input port to open
* @return the {@link MidiInputPort}
*/
public MidiInputPort openInputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
throw new IllegalArgumentException("input port number out of range");
}
synchronized (mInputPorts) {
if (mInputPorts[portNumber] == null) {
mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber);
}
return mInputPorts[portNumber];
}
}
/**
* Called to open a {@link MidiOutputPort} for the specified port number.
*
* @param portNumber the number of the output port to open
* @return the {@link MidiOutputPort}
*/
public MidiOutputPort openOutputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
throw new IllegalArgumentException("output port number out of range");
}
synchronized (mOutputPorts) {
if (mOutputPorts[portNumber] == null) {
mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber);
}
return mOutputPorts[portNumber];
}
}
/* package */ void connect(MidiReceiver receiver, int portNumber) {
synchronized (mReceivers) {
if (mReceivers[portNumber] == null) {
mReceivers[portNumber] = new ArrayList<MidiReceiver>();
}
mReceivers[portNumber].add(receiver);
if (mReceiverCount++ == 0) {
mThread.start();
}
}
}
/* package */ void disconnect(MidiReceiver receiver, int portNumber) {
synchronized (mReceivers) {
ArrayList<MidiReceiver> receivers = mReceivers[portNumber];
if (receivers != null && receivers.remove(receiver)) {
mReceiverCount--;
}
}
}
/* package */ boolean open() {
FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
try {
mInputStream = new FileInputStream(fd);
} catch (Exception e) {
Log.e(TAG, "could not create mInputStream", e);
return false;
}
try {
mOutputStream = new FileOutputStream(fd);
} catch (Exception e) {
Log.e(TAG, "could not create mOutputStream", e);
return false;
}
return true;
}
/* package */ void close() {
try {
if (mInputStream != null) {
mInputStream.close();
}
if (mOutputStream != null) {
mOutputStream.close();
}
mParcelFileDescriptor.close();
} catch (IOException e) {
}
}
/**
* Returns a {@link MidiDeviceInfo} object, which describes this device.
*
* @return the {@link MidiDeviceInfo} object
*/
public MidiDeviceInfo getInfo() {
return mDeviceInfo;
}
@Override
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
}
public static final Parcelable.Creator<MidiDevice> CREATOR =
new Parcelable.Creator<MidiDevice>() {
public MidiDevice createFromParcel(Parcel in) {
MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null);
ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null);
return new MidiDevice(deviceInfo, pfd);
}
public MidiDevice[] newArray(int size) {
return new MidiDevice[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mDeviceInfo, flags);
parcel.writeParcelable(mParcelFileDescriptor, flags);
}
/**
* Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
* timestamp is message timestamp to pack
* dest is buffer to pack into
* returns size of packed message
*
* @hide
*/
public static int packMessage(byte[] message, int offset, int size, long timestamp,
int portNumber, byte[] dest) {
// pack variable length message first
System.arraycopy(message, offset, dest, 0, size);
int destOffset = size;
// timestamp takes 8 bytes
for (int i = 0; i < 8; i++) {
dest[destOffset++] = (byte)timestamp;
timestamp >>= 8;
}
// portNumber is last
dest[destOffset++] = (byte)portNumber;
return destOffset;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns the offet of of MIDI message in packed buffer
*
* @hide
*/
public static int getMessageOffset(byte[] buffer, int bufferLength) {
// message is at start of buffer
return 0;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns size of MIDI message in packed buffer
*
* @hide
*/
public static int getMessageSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp and port number
return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*
* @hide
*/
public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
long timestamp = 0;
// timestamp follows variable length message data
int dataLength = getMessageSize(buffer, bufferLength);
for (int i = dataLength + 7; i >= dataLength; i--) {
// why can't Java deal with unsigned ints?
int b = buffer[i];
if (b < 0) b += 256;
timestamp = (timestamp << 8) | b;
}
return timestamp;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* unpacks port number from packed buffer
*
* @hide
*/
public static int getMessagePortNumber(byte[] buffer, int bufferLength) {
// timestamp follows variable length message data and timestamp
int dataLength = getMessageSize(buffer, bufferLength);
return buffer[dataLength + 8 /* sizeof(timestamp) */];
}
}
|