Virtualizer.java revision 2e7dd4bfc2460b99c37568a223a18e74b9cb8086
1d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen/*
2d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * Copyright (C) 2010 The Android Open Source Project
3d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen *
4d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * Licensed under the Apache License, Version 2.0 (the "License");
5d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * you may not use this file except in compliance with the License.
6d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * You may obtain a copy of the License at
7d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen *
8d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen *      http://www.apache.org/licenses/LICENSE-2.0
9d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen *
10d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * Unless required by applicable law or agreed to in writing, software
11d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * distributed under the License is distributed on an "AS IS" BASIS,
12d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * See the License for the specific language governing permissions and
14d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * limitations under the License.
15d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen */
16d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
17d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenpackage android.media.audiofx;
18d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
19d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport android.media.AudioDevice;
20d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport android.media.AudioFormat;
21d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport android.media.audiofx.AudioEffect;
22d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport android.util.Log;
23d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
24d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport java.nio.ByteBuffer;
25d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport java.nio.ByteOrder;
26d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenimport java.util.StringTokenizer;
27d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
28d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
29d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen/**
30d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * An audio virtualizer is a general name for an effect to spatialize audio channels. The exact
31d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * behavior of this effect is dependent on the number of audio input channels and the types and
32d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * number of audio output channels of the device. For example, in the case of a stereo input and
33d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * stereo headphone output, a stereo widening effect is used when this effect is turned on.
34d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>An application creates a Virtualizer object to instantiate and control a virtualizer engine
35d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * in the audio framework.
36d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>The methods, parameter types and units exposed by the Virtualizer implementation are directly
37d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/)
38d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * for the SLVirtualizerItf interface. Please refer to this specification for more details.
39d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>To attach the Virtualizer to a particular AudioTrack or MediaPlayer, specify the audio session
40d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * ID of this AudioTrack or MediaPlayer when constructing the Virtualizer.
41d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>NOTE: attaching a Virtualizer to the global audio output mix by use of session 0 is
42d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * deprecated.
43d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
44d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
45d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen * audio effects.
46d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen */
47d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
48d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chenpublic class Virtualizer extends AudioEffect {
49d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
50d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    private final static String TAG = "Virtualizer";
51d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    private final static boolean DEBUG = false;
52d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen
53d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    // These constants must be synchronized with those in
54d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    //        system/media/audio_effects/include/audio_effects/effect_virtualizer.h
55d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    /**
56d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen     * Is strength parameter supported by virtualizer engine. Parameter ID for getParameter().
57d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen     */
58d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    public static final int PARAM_STRENGTH_SUPPORTED = 0;
59d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen    /**
60d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen     * Virtualizer effect strength. Parameter ID for
61d5790d78880d4bd60be277ee20e53a851aa8c11Mike J. Chen     * {@link android.media.audiofx.Virtualizer.OnParameterChangeListener}
62     */
63    public static final int PARAM_STRENGTH = 1;
64    /**
65     * @hide
66     * Parameter ID to query the virtual speaker angles for a channel mask / device configuration.
67     */
68    public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2;
69    /**
70     * @hide
71     * Parameter ID to force the virtualization mode to be that of a specific device
72     */
73    public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3;
74    /**
75     * @hide
76     * Parameter ID to query the current virtualization mode.
77     */
78    public static final int PARAM_VIRTUALIZATION_MODE = 4;
79
80    /**
81     * Indicates if strength parameter is supported by the virtualizer engine
82     */
83    private boolean mStrengthSupported = false;
84
85    /**
86     * Registered listener for parameter changes.
87     */
88    private OnParameterChangeListener mParamListener = null;
89
90    /**
91     * Listener used internally to to receive raw parameter change event from AudioEffect super class
92     */
93    private BaseParameterListener mBaseParamListener = null;
94
95    /**
96     * Lock for access to mParamListener
97     */
98    private final Object mParamListenerLock = new Object();
99
100    /**
101     * Class constructor.
102     * @param priority the priority level requested by the application for controlling the Virtualizer
103     * engine. As the same engine can be shared by several applications, this parameter indicates
104     * how much the requesting application needs control of effect parameters. The normal priority
105     * is 0, above normal is a positive number, below normal a negative number.
106     * @param audioSession  system wide unique audio session identifier. The Virtualizer will
107     * be attached to the MediaPlayer or AudioTrack in the same audio session.
108     *
109     * @throws java.lang.IllegalStateException
110     * @throws java.lang.IllegalArgumentException
111     * @throws java.lang.UnsupportedOperationException
112     * @throws java.lang.RuntimeException
113     */
114    public Virtualizer(int priority, int audioSession)
115    throws IllegalStateException, IllegalArgumentException,
116           UnsupportedOperationException, RuntimeException {
117        super(EFFECT_TYPE_VIRTUALIZER, EFFECT_TYPE_NULL, priority, audioSession);
118
119        if (audioSession == 0) {
120            Log.w(TAG, "WARNING: attaching a Virtualizer to global output mix is deprecated!");
121        }
122
123        int[] value = new int[1];
124        checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value));
125        mStrengthSupported = (value[0] != 0);
126    }
127
128    /**
129     * Indicates whether setting strength is supported. If this method returns false, only one
130     * strength is supported and the setStrength() method always rounds to that value.
131     * @return true is strength parameter is supported, false otherwise
132     */
133    public boolean getStrengthSupported() {
134       return mStrengthSupported;
135    }
136
137    /**
138     * Sets the strength of the virtualizer effect. If the implementation does not support per mille
139     * accuracy for setting the strength, it is allowed to round the given strength to the nearest
140     * supported value. You can use the {@link #getRoundedStrength()} method to query the
141     * (possibly rounded) value that was actually set.
142     * @param strength strength of the effect. The valid range for strength strength is [0, 1000],
143     * where 0 per mille designates the mildest effect and 1000 per mille designates the strongest.
144     * @throws IllegalStateException
145     * @throws IllegalArgumentException
146     * @throws UnsupportedOperationException
147     */
148    public void setStrength(short strength)
149    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
150        checkStatus(setParameter(PARAM_STRENGTH, strength));
151    }
152
153    /**
154     * Gets the current strength of the effect.
155     * @return the strength of the effect. The valid range for strength is [0, 1000], where 0 per
156     * mille designates the mildest effect and 1000 per mille the strongest
157     * @throws IllegalStateException
158     * @throws IllegalArgumentException
159     * @throws UnsupportedOperationException
160     */
161    public short getRoundedStrength()
162    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
163        short[] value = new short[1];
164        checkStatus(getParameter(PARAM_STRENGTH, value));
165        return value[0];
166    }
167
168    /**
169     * Checks if a configuration is supported, and query the virtual speaker angles.
170     * @param inputChannelMask
171     * @param deviceType
172     * @param angles if non-null: array in which the angles will be written. If null, no angles
173     *    are returned
174     * @return true if the combination of channel mask and output device type is supported, false
175     *    otherwise
176     * @throws IllegalStateException
177     * @throws IllegalArgumentException
178     * @throws UnsupportedOperationException
179     */
180    private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles)
181            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
182        // parameter check
183        if (inputChannelMask == AudioFormat.CHANNEL_INVALID) {
184            throw (new IllegalArgumentException(
185                    "Virtualizer: illegal CHANNEL_INVALID channel mask"));
186        }
187        int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ?
188                AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask;
189        int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask);
190        if ((angles != null) && (angles.length < (nbChannels * 3))) {
191            Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask ("
192                    + nbChannels + ")");
193            throw (new IllegalArgumentException(
194                    "Virtualizer: array for channel / angle pairs is too small: is " + angles.length
195                    + ", should be " + (nbChannels * 3)));
196        }
197
198        ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4);
199        paramsConverter.order(ByteOrder.nativeOrder());
200        paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES);
201        // convert channel mask to internal native representation
202        paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask));
203        // convert Java device type to internal representation
204        paramsConverter.putInt(AudioDevice.convertDeviceTypeToInternalDevice(deviceType));
205        // allocate an array to store the results
206        byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/];
207
208        // call into the effect framework
209        int status = getParameter(paramsConverter.array(), result);
210        if (DEBUG) {
211            Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x"
212                    + Integer.toHexString(deviceType) + ") returns " + status);
213        }
214
215        if (status >= 0) {
216            if (angles != null) {
217                // convert and copy the results
218                ByteBuffer resultConverter = ByteBuffer.wrap(result);
219                resultConverter.order(ByteOrder.nativeOrder());
220                for (int i = 0 ; i < nbChannels ; i++) {
221                    // write the channel mask
222                    angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask(
223                            resultConverter.getInt((i * 4 * 3)));
224                    // write the azimuth
225                    angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4);
226                    // write the elevation
227                    angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8);
228                    if (DEBUG) {
229                        Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase()
230                                + " at az=" + angles[3*i+1] + "deg"
231                                + " elev="  + angles[3*i+2] + "deg");
232                    }
233                }
234            }
235            return true;
236        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
237            // a BAD_VALUE return from getParameter indicates the configuration is not supported
238            // don't throw an exception, just return false
239            return false;
240        } else {
241            // something wrong may have happened
242            checkStatus(status);
243        }
244        // unexpected virtualizer behavior
245        Log.e(TAG, "unexpected status code " + status
246                + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)");
247        return false;
248    }
249
250    /**
251     * Checks if the combination of a channel mask and device type is supported by this virtualizer.
252     * Some virtualizer implementations may only support binaural processing (i.e. only support
253     * headphone output), some may support transaural processing (i.e. for speaker output) for the
254     * built-in speakers. Use this method to query the virtualizer implementation capabilities.
255     * @param inputChannelMask the channel mask of the content to virtualize.
256     * @param deviceType the device type for which virtualization processing is to be performed.
257     *    Valid values are the device types defined in {@link AudioDevice}.
258     * @return true if the combination of channel mask and output device type is supported, false
259     *    otherwise.
260     *    <br>An indication that a certain channel mask is not supported doesn't necessarily mean
261     *    you cannot play content with that channel mask, it more likely implies the content will
262     *    be downmixed before being virtualized. For instance a virtualizer that only supports a
263     *    mask such as {@link AudioFormat#CHANNEL_OUT_STEREO}
264     *    will still be able to process content with a mask of
265     *    {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and
266     *    then will virtualize, as opposed to virtualizing each channel individually.
267     * @throws IllegalStateException
268     * @throws IllegalArgumentException
269     * @throws UnsupportedOperationException
270     */
271    public boolean canVirtualize(int inputChannelMask, int deviceType)
272            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
273        return getAnglesInt(inputChannelMask, deviceType, null);
274    }
275
276    /**
277     * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
278     * mask and device type.
279     * If the virtualization configuration (mask and device) is supported (see
280     * {@link #canVirtualize(int, int)}, the array angles will contain upon return the
281     * definition of each virtual speaker and its azimuth and elevation angles relative to the
282     * listener.
283     * <br>Note that in some virtualizer implementations, the angles may be strength-dependent.
284     * @param inputChannelMask the channel mask of the content to virtualize.
285     * @param deviceType the device type for which virtualization processing is to be performed.
286     *    Valid values are the device types defined in {@link AudioDevice}.
287     * @param angles a non-null array whose length is 3 times the number of channels in the channel
288     *    mask.
289     *    If the method indicates the configuration is supported, the array will contain upon return
290     *    triplets of values: for each channel <code>i</code> among the channels of the mask:
291     *    <ul>
292     *      <li>the element at index <code>3*i</code> in the array contains the speaker
293     *          identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li>
294     *      <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle
295     *          expressed in degrees, where 0 is the direction the listener faces, 180 is behind
296     *          the listener, and -90 is to her/his left,</li>
297     *      <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle
298     *          where +90 is directly above the listener, 0 is the horizontal plane, and -90 is
299     *          directly below the listener.</li>
300     * @return true if the combination of channel mask and output device type is supported, false
301     *    otherwise.
302     * @throws IllegalStateException
303     * @throws IllegalArgumentException
304     * @throws UnsupportedOperationException
305     */
306    public boolean getSpeakerAngles(int inputChannelMask, int deviceType, int[] angles)
307            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
308        if (angles == null) {
309            throw (new IllegalArgumentException(
310                    "Virtualizer: illegal null channel / angle array"));
311        }
312
313        return getAnglesInt(inputChannelMask, deviceType, angles);
314    }
315
316    /**
317     * Forces the virtualizer effect to use the processing mode used for the given device type.
318     * The effect must be enabled for the forced mode to be applied.
319     * @param deviceType one of the device types defined in {@link AudioDevice}.
320     *     Use {@link AudioDevice#DEVICE_TYPE_UNKNOWN} to return to the non-forced mode.
321     * @return true if the processing mode for the device type is supported, and it is successfully
322     *     set, or forcing was successfully disabled with {@link AudioDevice#DEVICE_TYPE_UNKNOWN},
323     *     false otherwise.
324     * @throws IllegalStateException
325     * @throws IllegalArgumentException
326     * @throws UnsupportedOperationException
327     */
328    public boolean forceVirtualizationMode(int deviceType)
329            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
330        // convert Java device type to internal representation
331        int internalDevice = AudioDevice.convertDeviceTypeToInternalDevice(deviceType);
332
333        int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice);
334
335        if (status >= 0) {
336            return true;
337        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
338            // a BAD_VALUE return from setParameter indicates the mode can't be forced to that
339            // of this device, don't throw an exception, just return false
340            return false;
341        } else {
342            // something wrong may have happened
343            checkStatus(status);
344        }
345        // unexpected virtualizer behavior
346        Log.e(TAG, "unexpected status code " + status
347                + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)");
348        return false;
349    }
350
351    /**
352     * @hide
353     * CANDIDATE FOR PUBLIC API
354     * Return the device type which reflects the virtualization mode being used, if any.
355     * @return a device type (as defined in {@link AudioDevice}) which reflects the virtualization
356     *     mode being used.
357     *     If virtualization is not active, the device type will be
358     *     {@link AudioDevice#DEVICE_TYPE_UNKNOWN}. Virtualization may not be active either because
359     *     the effect is not enabled or because the current output device is not compatible with
360     *     this virtualization implementation.
361     */
362    public int getVirtualizationMode() {
363        int[] value = new int[1];
364        int status = getParameter(PARAM_VIRTUALIZATION_MODE, value);
365        if (status >= 0) {
366            return AudioDevice.convertInternalDeviceToDeviceType(value[0]);
367        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
368            return AudioDevice.DEVICE_TYPE_UNKNOWN;
369        } else {
370            // something wrong may have happened
371            checkStatus(status);
372        }
373        // unexpected virtualizer behavior
374        Log.e(TAG, "unexpected status code " + status
375                + " after getParameter(PARAM_VIRTUALIZATION_MODE)");
376        return AudioDevice.DEVICE_TYPE_UNKNOWN;
377    }
378
379    /**
380     * The OnParameterChangeListener interface defines a method called by the Virtualizer when a
381     * parameter value has changed.
382     */
383    public interface OnParameterChangeListener  {
384        /**
385         * Method called when a parameter value has changed. The method is called only if the
386         * parameter was changed by another application having the control of the same
387         * Virtualizer engine.
388         * @param effect the Virtualizer on which the interface is registered.
389         * @param status status of the set parameter operation.
390         * @param param ID of the modified parameter. See {@link #PARAM_STRENGTH} ...
391         * @param value the new parameter value.
392         */
393        void onParameterChange(Virtualizer effect, int status, int param, short value);
394    }
395
396    /**
397     * Listener used internally to receive unformatted parameter change events from AudioEffect
398     * super class.
399     */
400    private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
401        private BaseParameterListener() {
402
403        }
404        public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
405            OnParameterChangeListener l = null;
406
407            synchronized (mParamListenerLock) {
408                if (mParamListener != null) {
409                    l = mParamListener;
410                }
411            }
412            if (l != null) {
413                int p = -1;
414                short v = -1;
415
416                if (param.length == 4) {
417                    p = byteArrayToInt(param, 0);
418                }
419                if (value.length == 2) {
420                    v = byteArrayToShort(value, 0);
421                }
422                if (p != -1 && v != -1) {
423                    l.onParameterChange(Virtualizer.this, status, p, v);
424                }
425            }
426        }
427    }
428
429    /**
430     * Registers an OnParameterChangeListener interface.
431     * @param listener OnParameterChangeListener interface registered
432     */
433    public void setParameterListener(OnParameterChangeListener listener) {
434        synchronized (mParamListenerLock) {
435            if (mParamListener == null) {
436                mParamListener = listener;
437                mBaseParamListener = new BaseParameterListener();
438                super.setParameterListener(mBaseParamListener);
439            }
440        }
441    }
442
443    /**
444     * The Settings class regroups all virtualizer parameters. It is used in
445     * conjuntion with getProperties() and setProperties() methods to backup and restore
446     * all parameters in a single call.
447     */
448    public static class Settings {
449        public short strength;
450
451        public Settings() {
452        }
453
454        /**
455         * Settings class constructor from a key=value; pairs formatted string. The string is
456         * typically returned by Settings.toString() method.
457         * @throws IllegalArgumentException if the string is not correctly formatted.
458         */
459        public Settings(String settings) {
460            StringTokenizer st = new StringTokenizer(settings, "=;");
461            int tokens = st.countTokens();
462            if (st.countTokens() != 3) {
463                throw new IllegalArgumentException("settings: " + settings);
464            }
465            String key = st.nextToken();
466            if (!key.equals("Virtualizer")) {
467                throw new IllegalArgumentException(
468                        "invalid settings for Virtualizer: " + key);
469            }
470            try {
471                key = st.nextToken();
472                if (!key.equals("strength")) {
473                    throw new IllegalArgumentException("invalid key name: " + key);
474                }
475                strength = Short.parseShort(st.nextToken());
476             } catch (NumberFormatException nfe) {
477                throw new IllegalArgumentException("invalid value for key: " + key);
478            }
479        }
480
481        @Override
482        public String toString() {
483            String str = new String (
484                    "Virtualizer"+
485                    ";strength="+Short.toString(strength)
486                    );
487            return str;
488        }
489    };
490
491
492    /**
493     * Gets the virtualizer properties. This method is useful when a snapshot of current
494     * virtualizer settings must be saved by the application.
495     * @return a Virtualizer.Settings object containing all current parameters values
496     * @throws IllegalStateException
497     * @throws IllegalArgumentException
498     * @throws UnsupportedOperationException
499     */
500    public Virtualizer.Settings getProperties()
501    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
502        Settings settings = new Settings();
503        short[] value = new short[1];
504        checkStatus(getParameter(PARAM_STRENGTH, value));
505        settings.strength = value[0];
506        return settings;
507    }
508
509    /**
510     * Sets the virtualizer properties. This method is useful when virtualizer settings have to
511     * be applied from a previous backup.
512     * @param settings a Virtualizer.Settings object containing the properties to apply
513     * @throws IllegalStateException
514     * @throws IllegalArgumentException
515     * @throws UnsupportedOperationException
516     */
517    public void setProperties(Virtualizer.Settings settings)
518    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
519        checkStatus(setParameter(PARAM_STRENGTH, settings.strength));
520    }
521}
522