Loading core/java/com/android/internal/midi/EventScheduler.java 0 → 100644 +236 −0 Original line number Original line Diff line number Diff line /* * 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 com.android.internal.midi; import java.util.SortedMap; import java.util.TreeMap; /** * Store arbitrary timestamped events using a Long timestamp. * Only one Thread can write into the buffer. * And only one Thread can read from the buffer. */ public class EventScheduler { private static final long NANOS_PER_MILLI = 1000000; private final Object lock = new Object(); private SortedMap<Long, FastEventQueue> mEventBuffer; private FastEventQueue mEventPool = null; private int mMaxPoolSize = 200; public EventScheduler() { mEventBuffer = new TreeMap<Long, FastEventQueue>(); } // If we keep at least one node in the list then it can be atomic // and non-blocking. private class FastEventQueue { // One thread takes from the beginning of the list. volatile SchedulableEvent mFirst; // A second thread returns events to the end of the list. volatile SchedulableEvent mLast; volatile long mEventsAdded; volatile long mEventsRemoved; FastEventQueue(SchedulableEvent event) { mFirst = event; mLast = mFirst; mEventsAdded = 1; mEventsRemoved = 0; } int size() { return (int)(mEventsAdded - mEventsRemoved); } /** * Do not call this unless there is more than one event * in the list. * @return first event in the list */ public SchedulableEvent remove() { // Take first event. mEventsRemoved++; SchedulableEvent event = mFirst; mFirst = event.mNext; return event; } /** * @param event */ public void add(SchedulableEvent event) { event.mNext = null; mLast.mNext = event; mLast = event; mEventsAdded++; } } /** * Base class for events that can be stored in the EventScheduler. */ public static class SchedulableEvent { private long mTimestamp; private SchedulableEvent mNext = null; /** * @param timestamp */ public SchedulableEvent(long timestamp) { mTimestamp = timestamp; } /** * @return timestamp */ public long getTimestamp() { return mTimestamp; } /** * The timestamp should not be modified when the event is in the * scheduling buffer. */ public void setTimestamp(long timestamp) { mTimestamp = timestamp; } } /** * Get an event from the pool. * Always leave at least one event in the pool. * @return event or null */ public SchedulableEvent removeEventfromPool() { SchedulableEvent event = null; if (mEventPool != null && (mEventPool.size() > 1)) { event = mEventPool.remove(); } return event; } /** * Return events to a pool so they can be reused. * * @param event */ public void addEventToPool(SchedulableEvent event) { if (mEventPool == null) { mEventPool = new FastEventQueue(event); // If we already have enough items in the pool then just // drop the event. This prevents unbounded memory leaks. } else if (mEventPool.size() < mMaxPoolSize) { mEventPool.add(event); } } /** * Add an event to the scheduler. Events with the same time will be * processed in order. * * @param event */ public void add(SchedulableEvent event) { synchronized (lock) { FastEventQueue list = mEventBuffer.get(event.getTimestamp()); if (list == null) { long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE : mEventBuffer.firstKey(); list = new FastEventQueue(event); mEventBuffer.put(event.getTimestamp(), list); // If the event we added is earlier than the previous earliest // event then notify any threads waiting for the next event. if (event.getTimestamp() < lowestTime) { lock.notify(); } } else { list.add(event); } } } private SchedulableEvent removeNextEventLocked(long lowestTime) { SchedulableEvent event; FastEventQueue list = mEventBuffer.get(lowestTime); // Remove list from tree if this is the last node. if ((list.size() == 1)) { mEventBuffer.remove(lowestTime); } event = list.remove(); return event; } /** * Check to see if any scheduled events are ready to be processed. * * @param timestamp * @return next event or null if none ready */ public SchedulableEvent getNextEvent(long time) { SchedulableEvent event = null; synchronized (lock) { if (!mEventBuffer.isEmpty()) { long lowestTime = mEventBuffer.firstKey(); // Is it time for this list to be processed? if (lowestTime <= time) { event = removeNextEventLocked(lowestTime); } } } // Log.i(TAG, "getNextEvent: event = " + event); return event; } /** * Return the next available event or wait until there is an event ready to * be processed. This method assumes that the timestamps are in nanoseconds * and that the current time is System.nanoTime(). * * @return event * @throws InterruptedException */ public SchedulableEvent waitNextEvent() throws InterruptedException { SchedulableEvent event = null; while (true) { long millisToWait = Integer.MAX_VALUE; synchronized (lock) { if (!mEventBuffer.isEmpty()) { long now = System.nanoTime(); long lowestTime = mEventBuffer.firstKey(); // Is it time for the earliest list to be processed? if (lowestTime <= now) { event = removeNextEventLocked(lowestTime); break; } else { // Figure out how long to sleep until next event. long nanosToWait = lowestTime - now; // Add 1 millisecond so we don't wake up before it is // ready. millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI); // Clip 64-bit value to 32-bit max. if (millisToWait > Integer.MAX_VALUE) { millisToWait = Integer.MAX_VALUE; } } } lock.wait((int) millisToWait); } } return event; } } core/java/com/android/internal/midi/MidiConstants.java 0 → 100644 +88 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2015 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 com.android.internal.midi; /** * MIDI related constants and static methods. */ public class MidiConstants { public static final byte STATUS_COMMAND_MASK = (byte) 0xF0; public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F; // Channel voice messages. public static final byte STATUS_NOTE_OFF = (byte) 0x80; public static final byte STATUS_NOTE_ON = (byte) 0x90; public static final byte STATUS_POLYPHONIC_AFTERTOUCH = (byte) 0xA0; public static final byte STATUS_CONTROL_CHANGE = (byte) 0xB0; public static final byte STATUS_PROGRAM_CHANGE = (byte) 0xC0; public static final byte STATUS_CHANNEL_PRESSURE = (byte) 0xD0; public static final byte STATUS_PITCH_BEND = (byte) 0xE0; // System Common Messages. public static final byte STATUS_SYSTEM_EXCLUSIVE = (byte) 0xF0; public static final byte STATUS_MIDI_TIME_CODE = (byte) 0xF1; public static final byte STATUS_SONG_POSITION = (byte) 0xF2; public static final byte STATUS_SONG_SELECT = (byte) 0xF3; public static final byte STATUS_TUNE_REQUEST = (byte) 0xF6; public static final byte STATUS_END_SYSEX = (byte) 0xF7; // System Real-Time Messages public static final byte STATUS_TIMING_CLOCK = (byte) 0xF8; public static final byte STATUS_START = (byte) 0xFA; public static final byte STATUS_CONTINUE = (byte) 0xFB; public static final byte STATUS_STOP = (byte) 0xFC; public static final byte STATUS_ACTIVE_SENSING = (byte) 0xFE; public static final byte STATUS_RESET = (byte) 0xFF; /** Number of bytes in a message nc from 8c to Ec */ public final static int CHANNEL_BYTE_LENGTHS[] = { 3, 3, 3, 3, 2, 2, 3 }; /** Number of bytes in a message Fn from F0 to FF */ public final static int SYSTEM_BYTE_LENGTHS[] = { 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; /********************************************************************/ public static int getBytesPerMessage(int command) { if ((command < 0x80) || (command > 0xFF)) { return 0; } else if (command >= 0xF0) { return SYSTEM_BYTE_LENGTHS[command & 0x0F]; } else { return CHANNEL_BYTE_LENGTHS[(command >> 4) - 8]; } } /** * @param msg * @param offset * @param count * @return true if the entire message is ActiveSensing commands */ public static boolean isAllActiveSensing(byte[] msg, int offset, int count) { // Count bytes that are not active sensing. int goodBytes = 0; for (int i = 0; i < count; i++) { byte b = msg[offset + i]; if (b != MidiConstants.STATUS_ACTIVE_SENSING) { goodBytes++; } } return (goodBytes == 0); } } media/java/android/media/midi/MidiDispatcher.java→core/java/com/android/internal/midi/MidiDispatcher.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -14,19 +14,20 @@ * limitations under the License. * limitations under the License. */ */ package android.media.midi; package com.android.internal.midi; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; import java.io.IOException; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** /** * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s. * Utility class for dispatching MIDI data to a list of {@link android.media.midi.MidiReceiver}s. * This class subclasses {@link MidiReceiver} and dispatches any data it receives * This class subclasses {@link android.media.midi.MidiReceiver} and dispatches any data it receives * to its receiver list. Any receivers that throw an exception upon receiving data will * to its receiver list. Any receivers that throw an exception upon receiving data will * be automatically removed from the receiver list, but no IOException will be returned * be automatically removed from the receiver list, but no IOException will be returned * from the dispatcher's {@link #onReceive} in that case. * from the dispatcher's {@link android.media.midi.MidiReceiver#onReceive} in that case. * * @hide */ */ public final class MidiDispatcher extends MidiReceiver { public final class MidiDispatcher extends MidiReceiver { Loading @@ -35,7 +36,7 @@ public final class MidiDispatcher extends MidiReceiver { private final MidiSender mSender = new MidiSender() { private final MidiSender mSender = new MidiSender() { /** /** * Called to connect a {@link MidiReceiver} to the sender * Called to connect a {@link android.media.midi.MidiReceiver} to the sender * * * @param receiver the receiver to connect * @param receiver the receiver to connect */ */ Loading @@ -44,7 +45,7 @@ public final class MidiDispatcher extends MidiReceiver { } } /** /** * Called to disconnect a {@link MidiReceiver} from the sender * Called to disconnect a {@link android.media.midi.MidiReceiver} from the sender * * * @param receiver the receiver to disconnect * @param receiver the receiver to disconnect */ */ Loading @@ -54,7 +55,7 @@ public final class MidiDispatcher extends MidiReceiver { }; }; /** /** * Returns the number of {@link MidiReceiver}s this dispatcher contains. * Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains. * @return the number of receivers * @return the number of receivers */ */ public int getReceiverCount() { public int getReceiverCount() { Loading @@ -62,7 +63,8 @@ public final class MidiDispatcher extends MidiReceiver { } } /** /** * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s * Returns a {@link android.media.midi.MidiSender} which is used to add and remove * {@link android.media.midi.MidiReceiver}s * to the dispatcher's receiver list. * to the dispatcher's receiver list. * @return the dispatcher's MidiSender * @return the dispatcher's MidiSender */ */ Loading core/java/com/android/internal/midi/MidiEventScheduler.java 0 → 100644 +119 −0 Original line number Original line Diff line number Diff line /* * 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 com.android.internal.midi; import android.media.midi.MidiReceiver; import java.io.IOException; /** * Add MIDI Events to an EventScheduler */ public class MidiEventScheduler extends EventScheduler { private static final String TAG = "MidiEventScheduler"; // Maintain a pool of scheduled events to reduce memory allocation. // This pool increases performance by about 14%. private final static int POOL_EVENT_SIZE = 16; private MidiReceiver mReceiver = new SchedulingReceiver(); private class SchedulingReceiver extends MidiReceiver { /** * Store these bytes in the EventScheduler to be delivered at the specified * time. */ @Override public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException { MidiEvent event = createScheduledEvent(msg, offset, count, timestamp); if (event != null) { add(event); } } } public static class MidiEvent extends SchedulableEvent { public int count = 0; public byte[] data; private MidiEvent(int count) { super(0); data = new byte[count]; } private MidiEvent(byte[] msg, int offset, int count, long timestamp) { super(timestamp); data = new byte[count]; System.arraycopy(msg, offset, data, 0, count); this.count = count; } @Override public String toString() { String text = "Event: "; for (int i = 0; i < count; i++) { text += data[i] + ", "; } return text; } } /** * Create an event that contains the message. */ private MidiEvent createScheduledEvent(byte[] msg, int offset, int count, long timestamp) { MidiEvent event; if (count > POOL_EVENT_SIZE) { event = new MidiEvent(msg, offset, count, timestamp); } else { event = (MidiEvent) removeEventfromPool(); if (event == null) { event = new MidiEvent(POOL_EVENT_SIZE); } System.arraycopy(msg, offset, event.data, 0, count); event.count = count; event.setTimestamp(timestamp); } return event; } /** * Return events to a pool so they can be reused. * * @param event */ @Override public void addEventToPool(SchedulableEvent event) { // Make sure the event is suitable for the pool. if (event instanceof MidiEvent) { MidiEvent midiEvent = (MidiEvent) event; if (midiEvent.data.length == POOL_EVENT_SIZE) { super.addEventToPool(event); } } } /** * This MidiReceiver will write date to the scheduling buffer. * @return the MidiReceiver */ public MidiReceiver getReceiver() { return mReceiver; } } core/java/com/android/internal/midi/MidiFramer.java 0 → 100644 +92 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2015 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 com.android.internal.midi; import android.media.midi.MidiReceiver; import java.io.IOException; /** * Convert stream of bytes to discrete messages. * * Parses the incoming bytes and then posts individual messages to the receiver * specified in the constructor. Short messages of 1-3 bytes will be complete. * System Exclusive messages may be posted in pieces. * * Resolves Running Status and * interleaved System Real-Time messages. */ public class MidiFramer extends MidiReceiver { public String TAG = "MidiFramer"; private MidiReceiver mReceiver; private byte[] mBuffer = new byte[3]; private int mCount; private int mRunningStatus; private int mNeeded; public MidiFramer(MidiReceiver receiver) { mReceiver = receiver; } public static String formatMidiData(byte[] data, int offset, int count) { String text = "MIDI+" + offset + " : "; for (int i = 0; i < count; i++) { text += String.format("0x%02X, ", data[offset + i]); } return text; } /* * @see android.midi.MidiReceiver#onPost(byte[], int, int, long) */ @Override public void onReceive(byte[] data, int offset, int count, long timestamp) throws IOException { // Log.i(TAG, formatMidiData(data, offset, count)); for (int i = 0; i < count; i++) { int b = data[offset] & 0xFF; if (b >= 0x80) { // status byte? if (b < 0xF0) { // channel message? mRunningStatus = (byte) b; mCount = 1; mNeeded = MidiConstants.getBytesPerMessage(b) - 1; } else if (b < 0xF8) { // system common? mBuffer[0] = (byte) b; mRunningStatus = 0; mCount = 1; mNeeded = MidiConstants.getBytesPerMessage(b) - 1; } else { // real-time? // Single byte message interleaved with other data. mReceiver.sendWithTimestamp(data, offset, 1, timestamp); } } else { // data byte mBuffer[mCount++] = (byte) b; if (--mNeeded == 0) { if (mRunningStatus != 0) { mBuffer[0] = (byte) mRunningStatus; } mReceiver.sendWithTimestamp(mBuffer, 0, mCount, timestamp); mNeeded = MidiConstants.getBytesPerMessage(mBuffer[0]) - 1; mCount = 1; } } ++offset; } } } Loading
core/java/com/android/internal/midi/EventScheduler.java 0 → 100644 +236 −0 Original line number Original line Diff line number Diff line /* * 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 com.android.internal.midi; import java.util.SortedMap; import java.util.TreeMap; /** * Store arbitrary timestamped events using a Long timestamp. * Only one Thread can write into the buffer. * And only one Thread can read from the buffer. */ public class EventScheduler { private static final long NANOS_PER_MILLI = 1000000; private final Object lock = new Object(); private SortedMap<Long, FastEventQueue> mEventBuffer; private FastEventQueue mEventPool = null; private int mMaxPoolSize = 200; public EventScheduler() { mEventBuffer = new TreeMap<Long, FastEventQueue>(); } // If we keep at least one node in the list then it can be atomic // and non-blocking. private class FastEventQueue { // One thread takes from the beginning of the list. volatile SchedulableEvent mFirst; // A second thread returns events to the end of the list. volatile SchedulableEvent mLast; volatile long mEventsAdded; volatile long mEventsRemoved; FastEventQueue(SchedulableEvent event) { mFirst = event; mLast = mFirst; mEventsAdded = 1; mEventsRemoved = 0; } int size() { return (int)(mEventsAdded - mEventsRemoved); } /** * Do not call this unless there is more than one event * in the list. * @return first event in the list */ public SchedulableEvent remove() { // Take first event. mEventsRemoved++; SchedulableEvent event = mFirst; mFirst = event.mNext; return event; } /** * @param event */ public void add(SchedulableEvent event) { event.mNext = null; mLast.mNext = event; mLast = event; mEventsAdded++; } } /** * Base class for events that can be stored in the EventScheduler. */ public static class SchedulableEvent { private long mTimestamp; private SchedulableEvent mNext = null; /** * @param timestamp */ public SchedulableEvent(long timestamp) { mTimestamp = timestamp; } /** * @return timestamp */ public long getTimestamp() { return mTimestamp; } /** * The timestamp should not be modified when the event is in the * scheduling buffer. */ public void setTimestamp(long timestamp) { mTimestamp = timestamp; } } /** * Get an event from the pool. * Always leave at least one event in the pool. * @return event or null */ public SchedulableEvent removeEventfromPool() { SchedulableEvent event = null; if (mEventPool != null && (mEventPool.size() > 1)) { event = mEventPool.remove(); } return event; } /** * Return events to a pool so they can be reused. * * @param event */ public void addEventToPool(SchedulableEvent event) { if (mEventPool == null) { mEventPool = new FastEventQueue(event); // If we already have enough items in the pool then just // drop the event. This prevents unbounded memory leaks. } else if (mEventPool.size() < mMaxPoolSize) { mEventPool.add(event); } } /** * Add an event to the scheduler. Events with the same time will be * processed in order. * * @param event */ public void add(SchedulableEvent event) { synchronized (lock) { FastEventQueue list = mEventBuffer.get(event.getTimestamp()); if (list == null) { long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE : mEventBuffer.firstKey(); list = new FastEventQueue(event); mEventBuffer.put(event.getTimestamp(), list); // If the event we added is earlier than the previous earliest // event then notify any threads waiting for the next event. if (event.getTimestamp() < lowestTime) { lock.notify(); } } else { list.add(event); } } } private SchedulableEvent removeNextEventLocked(long lowestTime) { SchedulableEvent event; FastEventQueue list = mEventBuffer.get(lowestTime); // Remove list from tree if this is the last node. if ((list.size() == 1)) { mEventBuffer.remove(lowestTime); } event = list.remove(); return event; } /** * Check to see if any scheduled events are ready to be processed. * * @param timestamp * @return next event or null if none ready */ public SchedulableEvent getNextEvent(long time) { SchedulableEvent event = null; synchronized (lock) { if (!mEventBuffer.isEmpty()) { long lowestTime = mEventBuffer.firstKey(); // Is it time for this list to be processed? if (lowestTime <= time) { event = removeNextEventLocked(lowestTime); } } } // Log.i(TAG, "getNextEvent: event = " + event); return event; } /** * Return the next available event or wait until there is an event ready to * be processed. This method assumes that the timestamps are in nanoseconds * and that the current time is System.nanoTime(). * * @return event * @throws InterruptedException */ public SchedulableEvent waitNextEvent() throws InterruptedException { SchedulableEvent event = null; while (true) { long millisToWait = Integer.MAX_VALUE; synchronized (lock) { if (!mEventBuffer.isEmpty()) { long now = System.nanoTime(); long lowestTime = mEventBuffer.firstKey(); // Is it time for the earliest list to be processed? if (lowestTime <= now) { event = removeNextEventLocked(lowestTime); break; } else { // Figure out how long to sleep until next event. long nanosToWait = lowestTime - now; // Add 1 millisecond so we don't wake up before it is // ready. millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI); // Clip 64-bit value to 32-bit max. if (millisToWait > Integer.MAX_VALUE) { millisToWait = Integer.MAX_VALUE; } } } lock.wait((int) millisToWait); } } return event; } }
core/java/com/android/internal/midi/MidiConstants.java 0 → 100644 +88 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2015 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 com.android.internal.midi; /** * MIDI related constants and static methods. */ public class MidiConstants { public static final byte STATUS_COMMAND_MASK = (byte) 0xF0; public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F; // Channel voice messages. public static final byte STATUS_NOTE_OFF = (byte) 0x80; public static final byte STATUS_NOTE_ON = (byte) 0x90; public static final byte STATUS_POLYPHONIC_AFTERTOUCH = (byte) 0xA0; public static final byte STATUS_CONTROL_CHANGE = (byte) 0xB0; public static final byte STATUS_PROGRAM_CHANGE = (byte) 0xC0; public static final byte STATUS_CHANNEL_PRESSURE = (byte) 0xD0; public static final byte STATUS_PITCH_BEND = (byte) 0xE0; // System Common Messages. public static final byte STATUS_SYSTEM_EXCLUSIVE = (byte) 0xF0; public static final byte STATUS_MIDI_TIME_CODE = (byte) 0xF1; public static final byte STATUS_SONG_POSITION = (byte) 0xF2; public static final byte STATUS_SONG_SELECT = (byte) 0xF3; public static final byte STATUS_TUNE_REQUEST = (byte) 0xF6; public static final byte STATUS_END_SYSEX = (byte) 0xF7; // System Real-Time Messages public static final byte STATUS_TIMING_CLOCK = (byte) 0xF8; public static final byte STATUS_START = (byte) 0xFA; public static final byte STATUS_CONTINUE = (byte) 0xFB; public static final byte STATUS_STOP = (byte) 0xFC; public static final byte STATUS_ACTIVE_SENSING = (byte) 0xFE; public static final byte STATUS_RESET = (byte) 0xFF; /** Number of bytes in a message nc from 8c to Ec */ public final static int CHANNEL_BYTE_LENGTHS[] = { 3, 3, 3, 3, 2, 2, 3 }; /** Number of bytes in a message Fn from F0 to FF */ public final static int SYSTEM_BYTE_LENGTHS[] = { 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; /********************************************************************/ public static int getBytesPerMessage(int command) { if ((command < 0x80) || (command > 0xFF)) { return 0; } else if (command >= 0xF0) { return SYSTEM_BYTE_LENGTHS[command & 0x0F]; } else { return CHANNEL_BYTE_LENGTHS[(command >> 4) - 8]; } } /** * @param msg * @param offset * @param count * @return true if the entire message is ActiveSensing commands */ public static boolean isAllActiveSensing(byte[] msg, int offset, int count) { // Count bytes that are not active sensing. int goodBytes = 0; for (int i = 0; i < count; i++) { byte b = msg[offset + i]; if (b != MidiConstants.STATUS_ACTIVE_SENSING) { goodBytes++; } } return (goodBytes == 0); } }
media/java/android/media/midi/MidiDispatcher.java→core/java/com/android/internal/midi/MidiDispatcher.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -14,19 +14,20 @@ * limitations under the License. * limitations under the License. */ */ package android.media.midi; package com.android.internal.midi; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; import java.io.IOException; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** /** * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s. * Utility class for dispatching MIDI data to a list of {@link android.media.midi.MidiReceiver}s. * This class subclasses {@link MidiReceiver} and dispatches any data it receives * This class subclasses {@link android.media.midi.MidiReceiver} and dispatches any data it receives * to its receiver list. Any receivers that throw an exception upon receiving data will * to its receiver list. Any receivers that throw an exception upon receiving data will * be automatically removed from the receiver list, but no IOException will be returned * be automatically removed from the receiver list, but no IOException will be returned * from the dispatcher's {@link #onReceive} in that case. * from the dispatcher's {@link android.media.midi.MidiReceiver#onReceive} in that case. * * @hide */ */ public final class MidiDispatcher extends MidiReceiver { public final class MidiDispatcher extends MidiReceiver { Loading @@ -35,7 +36,7 @@ public final class MidiDispatcher extends MidiReceiver { private final MidiSender mSender = new MidiSender() { private final MidiSender mSender = new MidiSender() { /** /** * Called to connect a {@link MidiReceiver} to the sender * Called to connect a {@link android.media.midi.MidiReceiver} to the sender * * * @param receiver the receiver to connect * @param receiver the receiver to connect */ */ Loading @@ -44,7 +45,7 @@ public final class MidiDispatcher extends MidiReceiver { } } /** /** * Called to disconnect a {@link MidiReceiver} from the sender * Called to disconnect a {@link android.media.midi.MidiReceiver} from the sender * * * @param receiver the receiver to disconnect * @param receiver the receiver to disconnect */ */ Loading @@ -54,7 +55,7 @@ public final class MidiDispatcher extends MidiReceiver { }; }; /** /** * Returns the number of {@link MidiReceiver}s this dispatcher contains. * Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains. * @return the number of receivers * @return the number of receivers */ */ public int getReceiverCount() { public int getReceiverCount() { Loading @@ -62,7 +63,8 @@ public final class MidiDispatcher extends MidiReceiver { } } /** /** * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s * Returns a {@link android.media.midi.MidiSender} which is used to add and remove * {@link android.media.midi.MidiReceiver}s * to the dispatcher's receiver list. * to the dispatcher's receiver list. * @return the dispatcher's MidiSender * @return the dispatcher's MidiSender */ */ Loading
core/java/com/android/internal/midi/MidiEventScheduler.java 0 → 100644 +119 −0 Original line number Original line Diff line number Diff line /* * 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 com.android.internal.midi; import android.media.midi.MidiReceiver; import java.io.IOException; /** * Add MIDI Events to an EventScheduler */ public class MidiEventScheduler extends EventScheduler { private static final String TAG = "MidiEventScheduler"; // Maintain a pool of scheduled events to reduce memory allocation. // This pool increases performance by about 14%. private final static int POOL_EVENT_SIZE = 16; private MidiReceiver mReceiver = new SchedulingReceiver(); private class SchedulingReceiver extends MidiReceiver { /** * Store these bytes in the EventScheduler to be delivered at the specified * time. */ @Override public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException { MidiEvent event = createScheduledEvent(msg, offset, count, timestamp); if (event != null) { add(event); } } } public static class MidiEvent extends SchedulableEvent { public int count = 0; public byte[] data; private MidiEvent(int count) { super(0); data = new byte[count]; } private MidiEvent(byte[] msg, int offset, int count, long timestamp) { super(timestamp); data = new byte[count]; System.arraycopy(msg, offset, data, 0, count); this.count = count; } @Override public String toString() { String text = "Event: "; for (int i = 0; i < count; i++) { text += data[i] + ", "; } return text; } } /** * Create an event that contains the message. */ private MidiEvent createScheduledEvent(byte[] msg, int offset, int count, long timestamp) { MidiEvent event; if (count > POOL_EVENT_SIZE) { event = new MidiEvent(msg, offset, count, timestamp); } else { event = (MidiEvent) removeEventfromPool(); if (event == null) { event = new MidiEvent(POOL_EVENT_SIZE); } System.arraycopy(msg, offset, event.data, 0, count); event.count = count; event.setTimestamp(timestamp); } return event; } /** * Return events to a pool so they can be reused. * * @param event */ @Override public void addEventToPool(SchedulableEvent event) { // Make sure the event is suitable for the pool. if (event instanceof MidiEvent) { MidiEvent midiEvent = (MidiEvent) event; if (midiEvent.data.length == POOL_EVENT_SIZE) { super.addEventToPool(event); } } } /** * This MidiReceiver will write date to the scheduling buffer. * @return the MidiReceiver */ public MidiReceiver getReceiver() { return mReceiver; } }
core/java/com/android/internal/midi/MidiFramer.java 0 → 100644 +92 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2015 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 com.android.internal.midi; import android.media.midi.MidiReceiver; import java.io.IOException; /** * Convert stream of bytes to discrete messages. * * Parses the incoming bytes and then posts individual messages to the receiver * specified in the constructor. Short messages of 1-3 bytes will be complete. * System Exclusive messages may be posted in pieces. * * Resolves Running Status and * interleaved System Real-Time messages. */ public class MidiFramer extends MidiReceiver { public String TAG = "MidiFramer"; private MidiReceiver mReceiver; private byte[] mBuffer = new byte[3]; private int mCount; private int mRunningStatus; private int mNeeded; public MidiFramer(MidiReceiver receiver) { mReceiver = receiver; } public static String formatMidiData(byte[] data, int offset, int count) { String text = "MIDI+" + offset + " : "; for (int i = 0; i < count; i++) { text += String.format("0x%02X, ", data[offset + i]); } return text; } /* * @see android.midi.MidiReceiver#onPost(byte[], int, int, long) */ @Override public void onReceive(byte[] data, int offset, int count, long timestamp) throws IOException { // Log.i(TAG, formatMidiData(data, offset, count)); for (int i = 0; i < count; i++) { int b = data[offset] & 0xFF; if (b >= 0x80) { // status byte? if (b < 0xF0) { // channel message? mRunningStatus = (byte) b; mCount = 1; mNeeded = MidiConstants.getBytesPerMessage(b) - 1; } else if (b < 0xF8) { // system common? mBuffer[0] = (byte) b; mRunningStatus = 0; mCount = 1; mNeeded = MidiConstants.getBytesPerMessage(b) - 1; } else { // real-time? // Single byte message interleaved with other data. mReceiver.sendWithTimestamp(data, offset, 1, timestamp); } } else { // data byte mBuffer[mCount++] = (byte) b; if (--mNeeded == 0) { if (mRunningStatus != 0) { mBuffer[0] = (byte) mRunningStatus; } mReceiver.sendWithTimestamp(mBuffer, 0, mCount, timestamp); mNeeded = MidiConstants.getBytesPerMessage(mBuffer[0]) - 1; mCount = 1; } } ++offset; } } }