JetPlayer.java revision 843ef36f7b96cc19ea7d2996b7c8661b41ec3452
1/*
2 * Copyright (C) 2008 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
17package android.media;
18
19
20import java.io.FileDescriptor;
21import java.lang.ref.WeakReference;
22import java.lang.CloneNotSupportedException;
23
24import android.content.res.AssetFileDescriptor;
25import android.os.Looper;
26import android.os.Handler;
27import android.os.Message;
28import android.util.AndroidRuntimeException;
29import android.util.Log;
30
31/**
32 * JetPlayer provides access to JET content playback and control.
33 *
34 * <p>Please refer to the JET Creator User Manual for a presentation of the JET interactive
35 * music concept and how to use the JetCreator tool to create content to be player by JetPlayer.
36 *
37 * <p>Use of the JetPlayer class is based around the playback of a number of JET segments
38 * sequentially added to a playback FIFO queue. The rendering of the MIDI content stored in each
39 * segment can be dynamically affected by two mechanisms:
40 * <ul>
41 * <li>tracks in a segment can be muted or unmuted at any moment, individually or through
42 *    a mask (to change the mute state of multiple tracks at once)</li>
43 * <li>parts of tracks in a segment can be played at predefined points in the segment, in order
44 *    to maintain synchronization with the other tracks in the segment. This is achieved through
45 *    the notion of "clips", which can be triggered at any time, but that will play only at the
46 *    right time, as authored in the corresponding JET file.</li>
47 * </ul>
48 * As a result of the rendering and playback of the JET segments, the user of the JetPlayer instance
49 * can receive notifications from the JET engine relative to:
50 * <ul>
51 * <li>the playback state,</li>
52 * <li>the number of segments left to play in the queue,</li>
53 * <li>application controller events (CC80-83) to mark points in the MIDI segments.</li>
54 * </ul>
55 *  Use {@link #getJetPlayer()} to construct a JetPlayer instance. JetPlayer is a singleton class.
56 *
57 */
58public class JetPlayer
59{
60    //--------------------------------------------
61    // Constants
62    //------------------------
63    /**
64     * The maximum number of simultaneous tracks. Use {@link #getMaxTracks()} to
65     * access this value.
66     */
67    private static int MAXTRACKS = 32;
68
69    // to keep in sync with the JetPlayer class constants
70    // defined in frameworks/base/include/media/JetPlayer.h
71    private static final int JET_EVENT                   = 1;
72    private static final int JET_USERID_UPDATE           = 2;
73    private static final int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
74    private static final int JET_PAUSE_UPDATE            = 4;
75
76    // to keep in sync with external/sonivox/arm-wt-22k/lib_src/jet_data.h
77    // Encoding of event information on 32 bits
78    private static final int JET_EVENT_VAL_MASK    = 0x0000007f; // mask for value
79    private static final int JET_EVENT_CTRL_MASK   = 0x00003f80; // mask for controller
80    private static final int JET_EVENT_CHAN_MASK   = 0x0003c000; // mask for channel
81    private static final int JET_EVENT_TRACK_MASK  = 0x00fc0000; // mask for track number
82    private static final int JET_EVENT_SEG_MASK    = 0xff000000; // mask for segment ID
83    private static final int JET_EVENT_CTRL_SHIFT  = 7;  // shift to get controller number to bit 0
84    private static final int JET_EVENT_CHAN_SHIFT  = 14; // shift to get MIDI channel to bit 0
85    private static final int JET_EVENT_TRACK_SHIFT = 18; // shift to get track ID to bit 0
86    private static final int JET_EVENT_SEG_SHIFT   = 24; // shift to get segment ID to bit 0
87
88    // to keep in sync with values used in external/sonivox/arm-wt-22k/Android.mk
89    // Jet rendering audio parameters
90    private static final int JET_OUTPUT_RATE = 22050; // _SAMPLE_RATE_22050 in Android.mk
91    private static final int JET_OUTPUT_CHANNEL_CONFIG =
92            AudioFormat.CHANNEL_CONFIGURATION_STEREO; // NUM_OUTPUT_CHANNELS=2 in Android.mk
93
94
95    //--------------------------------------------
96    // Member variables
97    //------------------------
98    /**
99     * Handler for jet events and status updates coming from the native code
100     */
101    private NativeEventHandler mEventHandler = null;
102
103    /**
104     * Looper associated with the thread that creates the AudioTrack instance
105     */
106    private Looper mInitializationLooper = null;
107
108    /**
109     * Lock to protect the event listener updates against event notifications
110     */
111    private final Object mEventListenerLock = new Object();
112
113    private OnJetEventListener mJetEventListener = null;
114
115    private static JetPlayer singletonRef;
116
117
118    //--------------------------------
119    // Used exclusively by native code
120    //--------------------
121    /**
122     * Accessed by native methods: provides access to C++ JetPlayer object
123     */
124    @SuppressWarnings("unused")
125    private int mNativePlayerInJavaObj;
126
127
128    //--------------------------------------------
129    // Constructor, finalize
130    //------------------------
131    /**
132     * Factory method for the JetPlayer class.
133     * @return the singleton JetPlayer instance
134     */
135    public static JetPlayer getJetPlayer() {
136        if (singletonRef == null) {
137            singletonRef = new JetPlayer();
138        }
139        return singletonRef;
140    }
141
142    /**
143     * Cloning a JetPlayer instance is not supported. Calling clone() will generate an exception.
144     */
145    public Object clone() throws CloneNotSupportedException {
146        // JetPlayer is a singleton class,
147        // so you can't clone a JetPlayer instance
148        throw new CloneNotSupportedException();
149    }
150
151
152    private JetPlayer() {
153
154        // remember which looper is associated with the JetPlayer instanciation
155        if ((mInitializationLooper = Looper.myLooper()) == null) {
156            mInitializationLooper = Looper.getMainLooper();
157        }
158
159        int buffSizeInBytes = AudioTrack.getMinBufferSize(JET_OUTPUT_RATE,
160                JET_OUTPUT_CHANNEL_CONFIG, AudioFormat.ENCODING_PCM_16BIT);
161
162        if ((buffSizeInBytes != AudioTrack.ERROR)
163                && (buffSizeInBytes != AudioTrack.ERROR_BAD_VALUE)) {
164
165            native_setup(new WeakReference<JetPlayer>(this),
166                    JetPlayer.getMaxTracks(),
167                    // bytes to frame conversion: sample format is ENCODING_PCM_16BIT, 2 channels
168                    // 1200 == minimum buffer size in frames on generation 1 hardware
169                    Math.max(1200, buffSizeInBytes / 4));
170        }
171    }
172
173
174    protected void finalize() {
175        native_finalize();
176    }
177
178
179    /**
180     * Stops the current JET playback, and releases all associated native resources.
181     * The object can no longer be used and the reference should be set to null
182     * after a call to release().
183     */
184    public void release() {
185        native_release();
186    }
187
188
189    //--------------------------------------------
190    // Getters
191    //------------------------
192    /**
193     * Returns the maximum number of simultaneous MIDI tracks supported by JetPlayer
194     */
195    public static int getMaxTracks() {
196        return JetPlayer.MAXTRACKS;
197    }
198
199
200    //--------------------------------------------
201    // Jet functionality
202    //------------------------
203    /**
204     * Loads a .jet file from a given path.
205     * @param path the path to the .jet file, for instance "/sdcard/mygame/music.jet".
206     * @return true if loading the .jet file was successful, false if loading failed.
207     */
208    public boolean loadJetFile(String path) {
209        return native_loadJetFromFile(path);
210    }
211
212
213    /**
214     * Loads a .jet file from an asset file descriptor.
215     * @param afd the asset file descriptor.
216     * @return true if loading the .jet file was successful, false if loading failed.
217     */
218    public boolean loadJetFile(AssetFileDescriptor afd) {
219        long len = afd.getLength();
220        if (len < 0) {
221            throw new AndroidRuntimeException("no length for fd");
222        }
223        return native_loadJetFromFileD(
224                afd.getFileDescriptor(), afd.getStartOffset(), len);
225    }
226
227    /**
228     * Closes the resource containing the JET content.
229     * @return true if successfully closed, false otherwise.
230     */
231    public boolean closeJetFile() {
232        return native_closeJetFile();
233    }
234
235
236    /**
237     * Starts playing the JET segment queue.
238     * @return true if rendering and playback is successfully started, false otherwise.
239     */
240    public boolean play() {
241        return native_playJet();
242    }
243
244
245    /**
246     * Pauses the playback of the JET segment queue.
247     * @return true if rendering and playback is successfully paused, false otherwise.
248     */
249    public boolean pause() {
250        return native_pauseJet();
251    }
252
253
254    /**
255     * Queues the specified segment in the JET queue.
256     * @param segmentNum the identifier of the segment.
257     * @param libNum the index of the sound bank associated with the segment. Use -1 to indicate
258     *    that no sound bank (DLS file) is associated with this segment, in which case JET will use
259     *    the General MIDI library.
260     * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
261     *    only play once. -1 means the segment will repeat indefinitely.
262     * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
263     *    Range is -12 to +12.
264     * @param muteFlags a bitmask to specify which MIDI tracks will be muted during playback. Bit 0
265     *    affects track 0, bit 1 affects track 1 etc.
266     * @param userID a value specified by the application that uniquely identifies the segment.
267     *    this value is received in the
268     *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
269     *    Normally, the application will keep a byte value that is incremented each time a new
270     *    segment is queued up. This can be used to look up any special characteristics of that
271     *    track including trigger clips and mute flags.
272     * @return true if the segment was successfully queued, false if the queue is full or if the
273     *    parameters are invalid.
274     */
275    public boolean queueJetSegment(int segmentNum, int libNum, int repeatCount,
276        int transpose, int muteFlags, byte userID) {
277        return native_queueJetSegment(segmentNum, libNum, repeatCount,
278                transpose, muteFlags, userID);
279    }
280
281
282    /**
283     * Queues the specified segment in the JET queue.
284     * @param segmentNum the identifier of the segment.
285     * @param libNum the index of the soundbank associated with the segment. Use -1 to indicate that
286     *    no sound bank (DLS file) is associated with this segment, in which case JET will use
287     *    the General MIDI library.
288     * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
289     *    only play once. -1 means the segment will repeat indefinitely.
290     * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
291     *    Range is -12 to +12.
292     * @param muteArray an array of booleans to specify which MIDI tracks will be muted during
293     *    playback. The value at index 0 affects track 0, value at index 1 affects track 1 etc.
294     *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
295     * @param userID a value specified by the application that uniquely identifies the segment.
296     *    this value is received in the
297     *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
298     *    Normally, the application will keep a byte value that is incremented each time a new
299     *    segment is queued up. This can be used to look up any special characteristics of that
300     *    track including trigger clips and mute flags.
301     * @return true if the segment was successfully queued, false if the queue is full or if the
302     *    parameters are invalid.
303     */
304    public boolean queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount,
305            int transpose, boolean[] muteArray, byte userID) {
306        if (muteArray.length != JetPlayer.getMaxTracks()) {
307            return false;
308        }
309        return native_queueJetSegmentMuteArray(segmentNum, libNum, repeatCount,
310                transpose, muteArray, userID);
311    }
312
313
314    /**
315     * Modifies the mute flags.
316     * @param muteFlags a bitmask to specify which MIDI tracks are muted. Bit 0 affects track 0,
317     *    bit 1 affects track 1 etc.
318     * @param sync if false, the new mute flags will be applied as soon as possible by the JET
319     *    render and playback engine. If true, the mute flags will be updated at the start of the
320     *    next segment. If the segment is repeated, the flags will take effect the next time
321     *    segment is repeated.
322     * @return true if the mute flags were successfully updated, false otherwise.
323     */
324    public boolean setMuteFlags(int muteFlags, boolean sync) {
325        return native_setMuteFlags(muteFlags, sync);
326    }
327
328
329    /**
330     * Modifies the mute flags for the current active segment.
331     * @param muteArray an array of booleans to specify which MIDI tracks are muted. The value at
332     *    index 0 affects track 0, value at index 1 affects track 1 etc.
333     *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
334     * @param sync if false, the new mute flags will be applied as soon as possible by the JET
335     *    render and playback engine. If true, the mute flags will be updated at the start of the
336     *    next segment. If the segment is repeated, the flags will take effect the next time
337     *    segment is repeated.
338     * @return true if the mute flags were successfully updated, false otherwise.
339     */
340    public boolean setMuteArray(boolean[] muteArray, boolean sync) {
341        if(muteArray.length != JetPlayer.getMaxTracks())
342            return false;
343        return native_setMuteArray(muteArray, sync);
344    }
345
346
347    /**
348     * Mutes or unmutes a single track.
349     * @param trackId the index of the track to mute.
350     * @param muteFlag set to true to mute, false to unmute.
351     * @param sync if false, the new mute flags will be applied as soon as possible by the JET
352     *    render and playback engine. If true, the mute flag will be updated at the start of the
353     *    next segment. If the segment is repeated, the flag will take effect the next time
354     *    segment is repeated.
355     * @return true if the mute flag was successfully updated, false otherwise.
356     */
357    public boolean setMuteFlag(int trackId, boolean muteFlag, boolean sync) {
358        return native_setMuteFlag(trackId, muteFlag, sync);
359    }
360
361
362    /**
363     * Schedules the playback of a clip.
364     * This will automatically update the mute flags in sync with the JET Clip Marker (controller
365     * 103). The parameter clipID must be in the range of 0-63. After the call to triggerClip, when
366     * JET next encounters a controller event 103 with bits 0-5 of the value equal to clipID and
367     * bit 6 set to 1, it will automatically unmute the track containing the controller event.
368     * When JET encounters the complementary controller event 103 with bits 0-5 of the value equal
369     * to clipID and bit 6 set to 0, it will mute the track again.
370     * @param clipId the identifier of the clip to trigger.
371     * @return true if the clip was successfully triggered, false otherwise.
372     */
373    public boolean triggerClip(int clipId) {
374        return native_triggerClip(clipId);
375    }
376
377
378    /**
379     * Empties the segment queue, and clears all clips that are scheduled for playback.
380     * @return true if the queue was successfully cleared, false otherwise.
381     */
382    public boolean clearQueue() {
383        return native_clearQueue();
384    }
385
386
387    //---------------------------------------------------------
388    // Internal class to handle events posted from native code
389    //------------------------
390    private class NativeEventHandler extends Handler
391    {
392        private JetPlayer mJet;
393
394        public NativeEventHandler(JetPlayer jet, Looper looper) {
395            super(looper);
396            mJet = jet;
397        }
398
399        @Override
400        public void handleMessage(Message msg) {
401            OnJetEventListener listener = null;
402            synchronized (mEventListenerLock) {
403                listener = mJet.mJetEventListener;
404            }
405            switch(msg.what) {
406            case JET_EVENT:
407                if (listener != null) {
408                    // call the appropriate listener after decoding the event parameters
409                    // encoded in msg.arg1
410                    mJetEventListener.onJetEvent(
411                            mJet,
412                            (short)((msg.arg1 & JET_EVENT_SEG_MASK)   >> JET_EVENT_SEG_SHIFT),
413                            (byte) ((msg.arg1 & JET_EVENT_TRACK_MASK) >> JET_EVENT_TRACK_SHIFT),
414                            // JETCreator channel numbers start at 1, but the index starts at 0
415                            // in the .jet files
416                            (byte)(((msg.arg1 & JET_EVENT_CHAN_MASK)  >> JET_EVENT_CHAN_SHIFT) + 1),
417                            (byte) ((msg.arg1 & JET_EVENT_CTRL_MASK)  >> JET_EVENT_CTRL_SHIFT),
418                            (byte)  (msg.arg1 & JET_EVENT_VAL_MASK) );
419                }
420                return;
421            case JET_USERID_UPDATE:
422                if (listener != null) {
423                    listener.onJetUserIdUpdate(mJet, msg.arg1, msg.arg2);
424                }
425                return;
426            case JET_NUMQUEUEDSEGMENT_UPDATE:
427                if (listener != null) {
428                    listener.onJetNumQueuedSegmentUpdate(mJet, msg.arg1);
429                }
430                return;
431            case JET_PAUSE_UPDATE:
432                if (listener != null)
433                    listener.onJetPauseUpdate(mJet, msg.arg1);
434                return;
435
436            default:
437                loge("Unknown message type " + msg.what);
438                return;
439            }
440        }
441    }
442
443
444    //--------------------------------------------
445    // Jet event listener
446    //------------------------
447    /**
448     * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
449     * playback engine.
450     * Notifications will be received in the same thread as the one in which the JetPlayer
451     * instance was created.
452     * @param listener
453     */
454    public void setEventListener(OnJetEventListener listener) {
455        setEventListener(listener, null);
456    }
457
458    /**
459     * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
460     * playback engine.
461     * Use this method to receive JET events in the Handler associated with another
462     * thread than the one in which you created the JetPlayer instance.
463     * @param listener
464     * @param handler the Handler that will receive the event notification messages.
465     */
466    public void setEventListener(OnJetEventListener listener, Handler handler) {
467        synchronized(mEventListenerLock) {
468
469            mJetEventListener = listener;
470
471            if (listener != null) {
472                if (handler != null) {
473                    mEventHandler = new NativeEventHandler(this, handler.getLooper());
474                } else {
475                    // no given handler, use the looper the AudioTrack was created in
476                    mEventHandler = new NativeEventHandler(this, mInitializationLooper);
477                }
478            } else {
479                mEventHandler = null;
480            }
481
482        }
483    }
484
485
486    /**
487     * Handles the notification when the JET engine generates an event.
488     */
489    public interface OnJetEventListener {
490        /**
491         * Callback for when the JET engine generates a new event.
492         *
493         * @param player the JET player the event is coming from
494         * @param segment 8 bit unsigned value
495         * @param track 6 bit unsigned value
496         * @param channel 4 bit unsigned value
497         * @param controller 7 bit unsigned value
498         * @param value 7 bit unsigned value
499         */
500        void onJetEvent(JetPlayer player,
501                short segment, byte track, byte channel, byte controller, byte value);
502        /**
503         * Callback for when JET's currently playing segment's userID is updated.
504         *
505         * @param player the JET player the status update is coming from
506         * @param userId the ID of the currently playing segment
507         * @param repeatCount the repetition count for the segment (0 means it plays once)
508         */
509        void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount);
510
511        /**
512         * Callback for when JET's number of queued segments is updated.
513         *
514         * @param player the JET player the status update is coming from
515         * @param nbSegments the number of segments in the JET queue
516         */
517        void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments);
518
519        /**
520         * Callback for when JET pause state is updated.
521         *
522         * @param player the JET player the status update is coming from
523         * @param paused indicates whether JET is paused (1) or not (0)
524         */
525        void onJetPauseUpdate(JetPlayer player, int paused);
526    }
527
528
529    //--------------------------------------------
530    // Native methods
531    //------------------------
532    private native final boolean native_setup(Object Jet_this,
533                int maxTracks, int trackBufferSize);
534    private native final void    native_finalize();
535    private native final void    native_release();
536    private native final boolean native_loadJetFromFile(String pathToJetFile);
537    private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len);
538    private native final boolean native_closeJetFile();
539    private native final boolean native_playJet();
540    private native final boolean native_pauseJet();
541    private native final boolean native_queueJetSegment(int segmentNum, int libNum,
542            int repeatCount, int transpose, int muteFlags, byte userID);
543    private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum,
544            int repeatCount, int transpose, boolean[] muteArray, byte userID);
545    private native final boolean native_setMuteFlags(int muteFlags, boolean sync);
546    private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync);
547    private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync);
548    private native final boolean native_triggerClip(int clipId);
549    private native final boolean native_clearQueue();
550
551    //---------------------------------------------------------
552    // Called exclusively by native code
553    //--------------------
554    @SuppressWarnings("unused")
555    private static void postEventFromNative(Object jetplayer_ref,
556            int what, int arg1, int arg2) {
557        //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
558        JetPlayer jet = (JetPlayer)((WeakReference)jetplayer_ref).get();
559
560        if ((jet != null) && (jet.mEventHandler != null)) {
561            Message m =
562                jet.mEventHandler.obtainMessage(what, arg1, arg2, null);
563            jet.mEventHandler.sendMessage(m);
564        }
565
566    }
567
568
569    //---------------------------------------------------------
570    // Utils
571    //--------------------
572    private final static String TAG = "JetPlayer-J";
573
574    private static void logd(String msg) {
575        Log.d(TAG, "[ android.media.JetPlayer ] " + msg);
576    }
577
578    private static void loge(String msg) {
579        Log.e(TAG, "[ android.media.JetPlayer ] " + msg);
580    }
581
582}
583