1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.midi;
18 
19 /**
20  * Uses multiple MidiEventSchedulers for waiting for events.
21  *
22  */
23 public class MidiEventMultiScheduler {
24     private MultiLockMidiEventScheduler[] mMidiEventSchedulers;
25     private int mNumEventSchedulers;
26     private int mNumClosedSchedulers = 0;
27     private final Object mMultiLock = new Object();
28 
29     private class MultiLockMidiEventScheduler extends MidiEventScheduler {
30         @Override
close()31         public void close() {
32             synchronized (mMultiLock) {
33                 mNumClosedSchedulers++;
34             }
35             super.close();
36         }
37 
38         @Override
getLock()39         protected Object getLock() {
40             return mMultiLock;
41         }
42 
isEventBufferEmptyLocked()43         public boolean isEventBufferEmptyLocked() {
44             return mEventBuffer.isEmpty();
45         }
46 
getLowestTimeLocked()47         public long getLowestTimeLocked() {
48             return mEventBuffer.firstKey();
49         }
50     }
51 
52     /**
53      * MidiEventMultiScheduler constructor
54      *
55      * @param numSchedulers the number of schedulers to create
56      */
MidiEventMultiScheduler(int numSchedulers)57     public MidiEventMultiScheduler(int numSchedulers) {
58         mNumEventSchedulers = numSchedulers;
59         mMidiEventSchedulers = new MultiLockMidiEventScheduler[numSchedulers];
60         for (int i = 0; i < numSchedulers; i++) {
61             mMidiEventSchedulers[i] = new MultiLockMidiEventScheduler();
62         }
63     }
64 
65     /**
66      * Waits for the next MIDI event. This will return true when it receives it.
67      * If all MidiEventSchedulers have been closed, this will return false.
68      *
69      * @return true if a MIDI event is received and false if all schedulers are closed.
70      */
waitNextEvent()71     public boolean waitNextEvent() throws InterruptedException {
72         synchronized (mMultiLock) {
73             while (true) {
74                 if (mNumClosedSchedulers >= mNumEventSchedulers) {
75                     return false;
76                 }
77                 long lowestTime = Long.MAX_VALUE;
78                 long now = System.nanoTime();
79                 for (MultiLockMidiEventScheduler eventScheduler : mMidiEventSchedulers) {
80                     if (!eventScheduler.isEventBufferEmptyLocked()) {
81                         lowestTime = Math.min(lowestTime,
82                                 eventScheduler.getLowestTimeLocked());
83                     }
84                 }
85                 if (lowestTime <= now) {
86                     return true;
87                 }
88                 long nanosToWait = lowestTime - now;
89                 // Add 1 millisecond so we don't wake up before it is
90                 // ready.
91                 long millisToWait = 1 + (nanosToWait / EventScheduler.NANOS_PER_MILLI);
92                 // Clip 64-bit value to 32-bit max.
93                 if (millisToWait > Integer.MAX_VALUE) {
94                     millisToWait = Integer.MAX_VALUE;
95                 }
96                 mMultiLock.wait(millisToWait);
97             }
98         }
99     }
100 
101     /**
102      * Gets the number of MidiEventSchedulers.
103      *
104      * @return the number of MidiEventSchedulers.
105      */
getNumEventSchedulers()106     public int getNumEventSchedulers() {
107         return mNumEventSchedulers;
108     }
109 
110     /**
111      * Gets a specific MidiEventScheduler based on the index.
112      *
113      * @param index the zero indexed index of a MIDI event scheduler
114      * @return a MidiEventScheduler
115      */
getEventScheduler(int index)116     public MidiEventScheduler getEventScheduler(int index) {
117         return mMidiEventSchedulers[index];
118     }
119 
120     /**
121      * Closes all event schedulers.
122      */
close()123     public void close() {
124         for (MidiEventScheduler eventScheduler : mMidiEventSchedulers) {
125             eventScheduler.close();
126         }
127     }
128 }
129