/*
* SoundInfo.java
* Transform
*
* Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.sound;
import java.io.IOException;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.coder.Context;
import com.flagstone.transform.coder.Copyable;
import com.flagstone.transform.coder.SWFDecoder;
import com.flagstone.transform.coder.SWFEncodeable;
import com.flagstone.transform.coder.SWFEncoder;
import com.flagstone.transform.exception.IllegalArgumentRangeException;
/**
* SoundInfo identifies a sound (previously defined using The DefineSound class)
* and controls how it is played.
*
* <p>
* SoundInfo defines how the sound fades in and out, whether it is repeated as
* well as specifying an envelope that provides a finer degree of control over
* the levels at which the sound is played.
* </p>
*
* <p>
* The in and out point specify the sample number which marks the point in time
* at which the sound stops increasing or starts decreasing in volume
* respectively. Sounds are played by the Flash player at 44.1KHz so the sample
* number also indicates the time when the total number of samples in the sound
* is taken into account.
* </p>
*
* <p>
* Not all the attributes are required to play a sound. Only the identifier and
* the mode is required. The other attributes are optional and may be added as a
* greater degree of control is required. The inPoint and outPoint attributes
* may be set to zero if the sound does not fade in or out respectively. The
* loopCount may be set to zero if a sound is being stopped. The envelopes
* may be left empty if no envelope is defined for the sound. The class provides
* different constructors to specify different sets of attributes.
* </p>
*
* @see DefineSound
*/
public final class SoundInfo implements SWFEncodeable, Copyable<SoundInfo> {
/** Format string used in toString() method. */
private static final String FORMAT = "SoundInfo: { identifier=%d; mode=%s;"
+ " inPoint=%d; outPoint=%d; loopCount=%d; envelopes=%s}";
/** Mode describes how the sound is controlled. */
public enum Mode {
/** Start playing the sound. */
START,
/** Start playing the sound or continues if it is already playing. */
CONTINUE,
/** Stop playing the sound. */
STOP;
}
/** The unique identifier of the sound that this info applies to. */
private int identifier;
/** Controls whether the sound starts or stops. */
private int mode;
/** The number of samples to fade the sound in. */
private Integer inPoint;
/** The number of samples to fade the sound out. */
private Integer outPoint;
/** The number of time the sound will be repeated. */
private Integer loopCount;
/** The envelope that controls how the sound is played. */
private Envelope envelope;
/**
* Creates and initialises a SoundInfo object using values encoded
* in the Flash binary format.
*
* @param uid the unique identifier for the sound definition - decoded by
* the parent object.
*
* @param coder
* an SWFDecoder object that contains the encoded Flash data.
*
* @throws IOException
* if an error occurs while decoding the data.
*/
public SoundInfo(final int uid, final SWFDecoder coder)
throws IOException {
identifier = uid;
final int info = coder.readByte();
mode = info & Coder.NIB1;
if ((info & Coder.BIT0) != 0) {
inPoint = coder.readInt();
}
if ((info & Coder.BIT1) != 0) {
outPoint = coder.readInt();
}
if ((info & Coder.BIT2) != 0) {
loopCount = coder.readUnsignedShort();
}
if ((info & Coder.BIT3) != 0) {
envelope = new Envelope(coder);
}
}
/**
* Creates a Sound object specifying how the sound is played and the number
* of times the sound is repeated.
*
* @param uid
* the unique identifier of the object that contains the sound
* data.
* @param aMode
* how the sound is synchronised when the frames are displayed:
* Play - do not play the sound if it is already playing and Stop
* - stop playing the sound.
* @param aCount
* the number of times the sound is repeated. May be set to zero
* if the sound will not be repeated.
* @param anEnvelope
* the Envelope that control the levels the sound is played.
*/
public SoundInfo(final int uid, final Mode aMode, final int aCount,
final Envelope anEnvelope) {
setIdentifier(uid);
setMode(aMode);
setLoopCount(aCount);
setEnvelope(anEnvelope);
}
/**
* Creates and initialises a SoundInfo object using the values copied
* from another SoundInfo object.
*
* @param object
* a SoundInfo object from which the values will be
* copied.
*/
public SoundInfo(final SoundInfo object) {
identifier = object.identifier;
mode = object.mode;
loopCount = object.loopCount;
inPoint = object.inPoint;
outPoint = object.outPoint;
if (object.envelope != null) {
envelope = object.envelope.copy();
}
}
/**
* Get the identifier of the sound to the played.
*
* @return the unique identifier of the sound.
*/
public int getIdentifier() {
return identifier;
}
/**
* Get the synchronisation mode: START - start playing the sound,
* CONTINUE - do not play the sound if it is already playing and STOP - stop
* playing the sound.
*
* @return the sound synchronisation mode.
*/
public Mode getMode() {
Mode value;
switch (mode) {
case 0:
value = Mode.START;
break;
case Coder.BIT4:
value = Mode.CONTINUE;
break;
case Coder.BIT5:
value = Mode.STOP;
break;
default:
throw new IllegalStateException();
}
return value;
}
/**
* Get the sample number at which the sound reaches full volume when
* fading in.
*
* @return the fade in point.
*/
public Integer getInPoint() {
return inPoint;
}
/**
* Get the sample number at which the sound starts to fade.
*
* @return the fade out point.
*/
public Integer getOutPoint() {
return outPoint;
}
/**
* Get the number of times the sound will be repeated.
*
* @return the number of loops.
*/
public Integer getLoopCount() {
return loopCount;
}
/**
* Get the Envelope that control the levels the sound is played.
*
* @return the sound envelope.
*/
public Envelope getEnvelope() {
return envelope;
}
/**
* Sets the identifier of the sound to the played.
*
* @param uid
* the identifier for the sound to be played. Must be in the
* range 1..65535.
*/
public void setIdentifier(final int uid) {
if ((uid < 1) || (uid > Coder.USHORT_MAX)) {
throw new IllegalArgumentRangeException(
1, Coder.USHORT_MAX, uid);
}
identifier = uid;
}
/**
* Sets how the sound is synchronised when the frames are displayed: START -
* start playing the sound, CONTINUE - do not play the sound if it is
* already playing and STOP - stop playing the sound.
*
* @param soundMode
* how the sound is played.
*/
public void setMode(final Mode soundMode) {
switch (soundMode) {
case START:
mode = 0;
break;
case CONTINUE:
mode = Coder.BIT4;
break;
case STOP:
mode = Coder.BIT5;
break;
default:
throw new IllegalArgumentException();
}
}
/**
* Sets the sample number at which the sound reaches full volume when fading
* in. May be set to zero if the sound does not fade in.
*
* @param aNumber
* the sample number which the sound fades in to.
*/
public void setInPoint(final Integer aNumber) {
if ((aNumber != null)
&& ((aNumber < 0) || (aNumber > Coder.USHORT_MAX))) {
throw new IllegalArgumentRangeException(0,
Coder.USHORT_MAX, aNumber);
}
inPoint = aNumber;
}
/**
* Sets the sample number at which the sound starts to fade. May be set to
* zero if the sound does not fade out.
*
* @param aNumber
* the sample number at which the sound starts to fade.
*/
public void setOutPoint(final Integer aNumber) {
if ((aNumber != null)
&& ((aNumber < 0) || (aNumber > Coder.USHORT_MAX))) {
throw new IllegalArgumentRangeException(0,
Coder.USHORT_MAX, aNumber);
}
outPoint = aNumber;
}
/**
* Sets the number of times the sound is repeated. May be set to zero if the
* sound will not be repeated.
*
* @param aNumber
* the number of times the sound is repeated.
*/
public void setLoopCount(final Integer aNumber) {
if ((aNumber != null)
&& ((aNumber < 0) || (aNumber > Coder.USHORT_MAX))) {
throw new IllegalArgumentRangeException(0,
Coder.USHORT_MAX, aNumber);
}
loopCount = aNumber;
}
/**
* Sets the Envelope that define the levels at which a sound is played over
* the duration of the sound. May be set to null if no envelope is defined.
*
* @param anEnvelope
* an Envelope object.
*/
public void setEnvelope(final Envelope anEnvelope) {
envelope = anEnvelope;
}
/** {@inheritDoc} */
public SoundInfo copy() {
return new SoundInfo(this);
}
@Override
public String toString() {
return String.format(FORMAT, identifier, mode, inPoint, outPoint,
loopCount, envelope);
}
/** {@inheritDoc} */
public int prepareToEncode(final Context context) {
// CHECKSTYLE:OFF
int length = 3;
if (inPoint != null) {
length += 4;
}
if (outPoint != null) {
length += 4;
}
if (loopCount != null) {
length += 2;
}
if (envelope != null) {
length += envelope.prepareToEncode(context);
}
return length;
// CHECKSTYLE:ON
}
/** {@inheritDoc} */
@SuppressWarnings("PMD.NPathComplexity")
public void encode(final SWFEncoder coder, final Context context)
throws IOException {
coder.writeShort(identifier);
int bits = mode;
bits |= envelope == null ? 0 : Coder.BIT3;
bits |= loopCount == null ? 0 : Coder.BIT2;
bits |= outPoint == null ? 0 : Coder.BIT1;
bits |= inPoint == null ? 0 : Coder.BIT0;
coder.writeByte(bits);
if (inPoint != null) {
coder.writeInt(inPoint);
}
if (outPoint != null) {
coder.writeInt(outPoint);
}
if (loopCount != null) {
coder.writeShort(loopCount);
}
if (envelope != null) {
envelope.encode(coder, context);
}
}
}