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