diff -r e5618cc85d74 -r 6c158198356e javauis/nokiasound/javasrc/com/nokia/mid/sound/Sound.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javauis/nokiasound/javasrc/com/nokia/mid/sound/Sound.java Thu Aug 19 09:48:13 2010 +0300 @@ -0,0 +1,698 @@ +/* +* Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: Provides Sound API for playing tones and digitized audio. +* +*/ + + +package com.nokia.mid.sound; + +import com.nokia.mj.impl.rt.support.Finalizer; +import java.util.Vector; +import com.nokia.mj.impl.utils.Logger; +import java.lang.Thread; + +/** + *

+ * Provides simple Sound API for playing tones and digitized audio. + *

+ * Since MIDP doesn't have any Sound API for games there is a need + * for proprietary sound extension to support devices audio + * capabilities. Every implementation has capability to produce tone + * sounds (e.g. ringing tones), this is the minimum support. Currently + * some products support or will support also digitized audio formats. + * The Game sound API will support both buzzer and digitized audio. + * Buzzer must be supported by all implementations. If implementation + * doesn't have buzzer the buzzer tones are emulated. + *

+ * Since implementations have different audio capabilities, + * application can query which audio formats are supported by + * implementation by calling {@link #getSupportedFormats()}. + *

+ * All implementations need to support at least tone based sounds + * (type FORMAT_TONE) via {@link #Sound(int freq, long duration)} and + * {@link #init(int freq, long duration)}. In addition all implementations + * must support Smart messaging ringingtone format (type FORMAT_TONE) + * via {@link #Sound(byte[] data, int type)} and + * {@link #init(byte[] data, int type) }. + *

+ * Note that there is also work going on with Multimedia API that + * is done in JCP as + * JSR 135. + * The standard Multimedia API + * will replace the proprietary Game Sound API when it is ready. However + * Sound API will be supported also later on but probably it will be + * stated as deprecated. + *

+ * @version 1.1 + * @see com.nokia.mid.ui.DeviceControl + * @since 1.0 + */ + +public class Sound +{ + + /** + * Tone based format is used. + * + * init(int freq, int duration) puts sound into this format. + * @since 1.0 + * + */ + public static final int FORMAT_TONE = 1; + + /** + * Content is in WAV format. + * @since 1.0 + * + */ + public static final int FORMAT_WAV = 5; + + /** + * Sound is playing. + * @since 1.0 + * + */ + public static final int SOUND_PLAYING = 0; + + /** + * Sound is stopped. + * @since 1.0 + * + */ + public static final int SOUND_STOPPED = 1; + + /** + * Sound is uninitialized (released). + * @since 1.0 + */ + public static final int SOUND_UNINITIALIZED = 3; + + /** + * Sound is reinitialising + */ + private static final int SOUND_REINITIALISING = 4; + + private static final int FORMAT_BEEP = 2; + private static final int NOT_SUPPORTED_ERROR = 3; + + private static final int ERR_NOT_READY = -18; + private static final int ERR_ARGUMENT = -6; + + private int iHandle; + + private Finalizer iFinalizer; + private int iCurrentType; + private int iState; + private int iGain = -1; + + Vector iSoundListeners = new Vector(); + + private static Sound iPlayingSound; + + static + { + com.nokia.mj.impl.rt.support.Jvm.loadSystemLibrary("javanokiasound"); + } + + /** + * Constructors initialize the Sound object so that it is ready for + * playback. This constructor is used for initializing Sound + * object based on byte array data. The data should contain the data + * presented in the data format specified by type parameter. The Sound + * class defines also generally supported types as constants. + *

+ * All implementations need to support at least Nokia + * Smart Messaging, Over the Air (OTA) ringingtone format. + * The type of this format is FORMAT_TONE. + *

+ * Note: some implementations can't throw exceptions about + * sound data being corrupted or illegal during construction. + * This will result that IllagalArgumentException is delayed until + * play(int loop) method is called. Applications thus need to except + * that IllegalArgumentException is thrown in this method or during + * play method call. + *

+ * @throws java.lang.IllegalArgumentException if the data can not be + recognized to + * given type or the type is unsupported or unknown + * @throws java.lang.NullPointerException if the data is null + * @since 1.0 + * + */ + public Sound(byte[] data, int type) + { + Logger.LOG(Logger.EJavaUI, Logger.EInfo, "Sound Constructor"); + iFinalizer = registerForFinalization(); + + iHandle = _create(); + + iState = SOUND_UNINITIALIZED; + + init(data, type); + } + + /** + * Constructors initialize the Sound object so that it is ready for + * playback. Sound is initialized as a simple tone based sound. + *

+ * See method {@link #init(int freq, long duration)} for + * freq value descriptions. See also a note on exceptions semantics in + * {@link #init(int freq, long duration)}. + * + * @param freq a frequency value + * @param duration the duration of the tone in milliseconds + * @throws java.lang.IllegalArgumentException if parameter values are + * illegal, freq is not in given range or duration is negative or zero + * @since 1.0 + */ + public Sound(int freq, long duration) + { + + iFinalizer = registerForFinalization(); + iHandle = _create(); + + iState = SOUND_UNINITIALIZED; + + init(freq, duration); + } + + /** + * Called when this object is finalized, frees native resources + */ + + public Finalizer registerForFinalization() + { + + return new Finalizer() + { + public void finalizeImpl() + { + doFinalize(); + } + }; + } + + void doFinalize() + { + + if (iFinalizer == null) + { + return; + } + iFinalizer = null; + + if (iHandle > 0) + { + _dispose(iHandle); + } + } + + /** + * Releases audio resources reserved by this object. After object + * is released it goes to uninitialized state. This method should + * be called when Sound object is not needed anymore. + * @since 1.0 + */ + public void release() + { + if ((iState != SOUND_UNINITIALIZED) && + (iState != SOUND_REINITIALISING)) + { + iState = SOUND_REINITIALISING; + soundStateChanged(SOUND_UNINITIALIZED); + } + + _release(iHandle); + + iState = SOUND_UNINITIALIZED; + + } + + /** + * Initializes Sound to play a simple beep. + *

+ * Note: some implementations may not support the full frequency + * scale defined in table below. They will throw + * IllegalArgumentException instead for unsupported values. The + * exception may also be delayed + * until the play(int loop) method is called. + *

+ * Following table describes some freq argument + * values: + *

+     * Description            Frequency
+     * Freq off               0
+     * Ring freq A0           220
+     * Ring freq B0b          233
+     * Ring freq B0           247
+     * Ring freq C0           262
+     * Ring freq D0b          277
+     * Ring freq D0           294
+     * Ring freq E0b          311
+     * Ring freq E0           330
+     * Ring freq F0           349
+     * Ring freq G0b          370
+     * Ring freq G0           392
+     * Ring freq A1b          416
+     * Ring freq A1           440
+     * Ring freq B1b          466
+     * Ring freq B1           494
+     * Ring freq C1           523
+     * Ring freq D1b          554
+     * Ring freq D1           587
+     * Ring freq E1b          622
+     * Ring freq E1           659
+     * Ring freq F1           698
+     * Ring freq G1b          740
+     * Ring freq G1           784
+     * Ring freq A2b          831
+     * Ring freq A2           880
+     * Ring freq B2b          932
+     * Ring freq B2           988
+     * Ring freq C2           1047
+     * Ring freq D2b          1109
+     * Ring freq D2           1175
+     * Ring freq E2b          1245
+     * Ring freq E2           1319
+     * Ring freq F2           1397
+     * Ring freq G2b          1480
+     * Ring freq G2           1568
+     * Ring freq A3b          1661
+     * Ring freq A3           1760
+     * Ring freq B3b          1865
+     * Ring freq B3           1976
+     * Ring freq C3           2093
+     * Ring freq D3b          2217
+     * Ring freq D3           2349
+     * Ring freq E3b          2489
+     * Ring freq E3           2637
+     * Ring freq F3           2794
+     * Ring freq G3b          2960
+     * Ring freq G3           3136
+     * Ring freq A4b          3322
+     * Ring freq A4           3520
+     * Ring freq B4b          3729
+     * Ring freq B4           3951
+     * Ring freq C4           4186
+     * Ring freq D4b          4434
+     * Ring freq D4           4698
+     * Ring freq E4b          4978
+     * Ring freq E4           5274
+     * Ring freq F4           5588
+     * Ring freq G4b          5920
+     * Ring freq G4           6272
+     * Ring freq A5b          6644
+     * Ring freq A5           7040
+     * Ring freq B5b          7458
+     * Ring freq B5           7902
+     * Ring freq C5           8372
+     * Ring freq D5b          8870
+     * Ring freq D5           9396
+     * Ring freq E5b          9956
+     * Ring freq E5           10548
+     * Ring freq F5           11176
+     * Ring freq G5b          11840
+     * Ring freq G5           12544
+     * Ring freq A6b          13288
+     *
+     * 
+ * + * @param duration length of the beep in milliseconds + * @param freq frequency to be played + * @throws java.lang.IllegalArgumentException if parameter values are + * illegal, freq is not in given range or duration is negative or zero + * @since 1.0 + */ + public void init(int freq, long duration) + { + if (duration < 1 || duration > 10000000) + { + throw(new IllegalArgumentException( + "Bad duration value, must be 1-10000000")); + } + if (freq < 0 || freq > 15000) + { + throw(new IllegalArgumentException( + "Bad frequency value, must be 0-15000")); + } + // if the uninitialised event is sent from native side, it reaches + // listener too late in TCK test sound8004, thus we send the event + // already here + if ((iState != SOUND_UNINITIALIZED) && + (iState != SOUND_REINITIALISING)) + { + iState = SOUND_REINITIALISING; + soundStateChanged(SOUND_UNINITIALIZED); + } // end of if (iState != SOUND_UNINITIALIZED) + + iCurrentType = FORMAT_BEEP; + int err = _init(iHandle, iCurrentType, null, freq, duration); + if (err == ERR_NOT_READY) + { + throw new RuntimeException(Integer.toString(err)); + } + else if (err == ERR_ARGUMENT) + { + throw new IllegalArgumentException("Data is invalid"); + } + iState = SOUND_STOPPED; + } + + /** + * Initializes Sound object based on byte + * array data. The data should contain the data presented in the data + * format specified by type parameter. The Sound class defines also + * generally supported types as constants. + *

+ * All implementations need to support at least Nokia + * Smart Messaging, Over the Air (OTA) ringingtone format. + * The type of this format is FORMAT_TONE. + *

+ * Note: some implementations can't throw exceptions about + * sound data being corrupted or illegal during this method call. + * This will result that IllagalArgumentException is delayed until + * play(int loop) method is called. Applications thus need to except + * that IllegalArgumentException is thrown in this method or during + * play method call. + *

+ * @param data a byte array containing the data to be played + * @param type type of the audio + * @throws java.lang.IllegalArgumentException if the data can not be + recognized to + * given type or the type is unsupported or unknown + * @throws java.lang.NullPointerException if the data is null + * @since 1.0 + */ + public void init(byte[] data, int type) + { + if (!(type == FORMAT_WAV || type == FORMAT_TONE)) + { + throw(new IllegalArgumentException("Type is not supported")); + } + if (data == null) + { + throw(new NullPointerException("Data is null")); + } + + if ((iState != SOUND_UNINITIALIZED) && + (iState != SOUND_REINITIALISING)) + { + iState = SOUND_REINITIALISING; + soundStateChanged(SOUND_UNINITIALIZED); + } // end of if (iState != SOUND_UNINITIALIZED) + + iCurrentType = type; + int err = _init(iHandle, iCurrentType, data, 0, 0); + if (err == ERR_NOT_READY || err == ERR_ARGUMENT ) + { + throw new IllegalArgumentException("Data is invalid"); + } + + iState = SOUND_STOPPED; + } + + + /** + * Get the current state of the Sound object. + * + * @return current state, SOUND_PLAYING, SOUND_STOPPED or + SOUND_UNINITIALIZED + * @since 1.0 + * + */ + public int getState() + { + if (iState == SOUND_REINITIALISING) + { + return SOUND_UNINITIALIZED; + } + + iState = _getState(iHandle); + switch (iState) + { + case(0): // ENotReady + case(4): // EInitialising + iState = SOUND_UNINITIALIZED; + break; + case(1): // EReadyToPlay + iState = SOUND_STOPPED; + break; + case(2): // EPlaying + iState = SOUND_PLAYING; + break; + default: + } + return iState; + } + + /** + * This method is used for starting the playback from the beginning of a + * sound object. The loop parameter defined the loop count for playback. + * Argument zero (0) means continuos looping. For uninitialized sound the + * play method doesn't do anything and silently returns. For stopped and + * playing sounds the playback starts from beginning of the sound with new + * looping information. + *

+ * This method will throw IllegalStateException if playback cannot be + * started since all channels are in use, or playback is not possible + * because there is more higher priority system sounds being played. + *

+ * If Sound playback is possible this method will return immediately and + * thus will not block the calling thread during the playback. If any error + * that prevents the playback is encountered during the playback, the + * playback is silently stopped as if called to the stop method. + * + * @param number number of times audio is played. Value 0 plays audio in + * continous loop. + * @throws java.lang.IllegalStateException if the sound object cannot be + * played because all the channels are already in use. + * @throws java.lang.IllegalArgumentException if the loop value is negative, + * or if sound values/date is illegal or corrupted. + * @since 1.0 + * + */ + public void play(int loop) throws IllegalArgumentException + { + if (loop < 0) + { + throw(new IllegalArgumentException("Negative loop value")); + } + if (iState == SOUND_REINITIALISING) + { + return; + } // end of if (iState == SOUND_REINITIALISING) + + if (iPlayingSound != null) + { + if (iPlayingSound.getState() == SOUND_PLAYING) + { + iPlayingSound.stop(); + } + } // end of if (iPlayingSound != null) + + int error = _play(iHandle, loop); + if ((error == NOT_SUPPORTED_ERROR)) + { + throw(new IllegalArgumentException("Sound is not supported")); + } + iPlayingSound = this; + } + + /** + * The method will stop the sound playback, storing the current position. + * For sound that has never been started (may be uninitialized), or is + * currently being stopped the method call doesn't do anything and returns + * silently. + * + * Note that for tone based sounds it is not possible to resume from + * position the sound was stopped at, to be specific, stop will reset + * the position to the beginning of the sound. + * @since 1.0 + */ + public void stop() + { + if (iState == SOUND_REINITIALISING) + { + return; + } // end of if (iState == SOUND_REINITIALISING) + _stop(iHandle); + } + + /** + * The method will continue the stopped sound object from the position it + * was stopped to. For sound that has never been started (may be + * uninitialized), or is currently being played the method call doesn't + * do anything. + *

+ * Note: For tone based sounds the resume starts the sound from the + * beginning of the sound clip. + * @since 1.0 + * + */ + public void resume() + { + if (iState == SOUND_REINITIALISING) + { + return; + } // end of if (iState == SOUND_REINITIALISING) + _resume(iHandle); + } + + /** + * Sets the gain for the sound object. The gain is a value between + * 0 and 255. Implementation scales the gain value to the limits it + * supports. Notice that any gain value > 0 should result a gain + * value > 0. If the gain is smaller than this minimum value then + * gain is set to 0, if the gain greater than this maximum value + * then the gain is set to maximum value (255). + * + * @param gain gain value: 0 - 255 + * @throws java.lang.IllegalArgumentException if the gain not 0 - 255 + * @since 1.0 + */ + public void setGain(int gain) + { + if (iState == SOUND_REINITIALISING) + { + return; + } // end of if (iState == SOUND_REINITIALISING) + if (gain < 0) + { + gain = 0; + } + else if (gain > 255) + { + gain = 255; + } + iGain = gain; + _setVolume(iHandle, iGain); + } + + /** + * Get the gain (or volume) of Sound object. The gain is a value + * between 0 and 255. System returns a scaled value based on the + * limits it supports. Notice that any system gain value > 0 should + * return a gain value > 0. + * + * @return gain value 0 - 255 + * @since 1.0 + * + */ + public int getGain() + { + if (iGain == -1) + { + return _volume(iHandle); + } + // we have previously set gain + return iGain; + } + + /** + * Returns number of concurrent sounds the device can play for + * specific audio type. Returns 1 if only one sound can be played + * at a time. Notice that most types use same channel resources. + * @return total number of available channels. + * @param type the media type + * @throws java.lang.IllegalArgumentException if the type is unsupported + * or unknown + * @since 1.0 + */ + public static int getConcurrentSoundCount(int type) + { + if ((type != FORMAT_TONE) && (type != FORMAT_WAV)) + { + throw(new IllegalArgumentException("Type is not supported")); + } + + return 1; + } + + /** + * Returns the supported audio formats as an int array. + * + * @return an array containing supported audio formats as + * int values (e.g. FORMAT_TONE, FORMAT_WAV), + * or an empty array if no audio formats are supported. + * @since 1.0 + */ + static public int[] getSupportedFormats() + { + return(new int[] { FORMAT_TONE, FORMAT_WAV }); + } + + /** + * Registeres a listener for playback state notifications. + * @see com.nokia.mid.sound.SoundListener + * @param listener a listener that is notified when state + * changes occur or null if listener is to be + * removed. + * @since 1.0 + * + */ + public void setSoundListener(SoundListener listener) + { + iSoundListeners.addElement(listener); + } + + /** + * Callback method when sound state changes + * + */ + public void soundStateChanged(final int event) + { + /* + for(int i = 0; i < iSoundListeners.size(); i++) + { + ((SoundListener)iSoundListeners.elementAt(i)).soundStateChanged(this, event); + } + */ + //Notify SoundState Listeners in a separate thread, so that application doesn't + //block main thread + new Thread(new Runnable() + { + public void run() + { + notifySoundStateListeners(event); + } + }).start(); + } + + /** + * Notify Sound State Listeners + */ + public synchronized void notifySoundStateListeners(int event) + { + for (int i = 0; i < iSoundListeners.size(); i++) + { + ((SoundListener)iSoundListeners.elementAt(i)).soundStateChanged(this, event); + } + } + + private native void _dispose(int aHandle); + private native int _create(); + private native int _init(int aHandle, int aType, + byte[] aData, + int aFrequency, long aDuration); + private native void _release(int aHandle); + private native int _play(int aHandle, int aLoop); + private native void _stop(int aHandle); + private native void _resume(int aHandle); + private native void _setVolume(int aHandle, int aVolume); + private native int _volume(int aHandle); + private native int _getState(int aHandle); + +} //End of Sound class +