pyfmodex/utils.py
def prepare_str(string, encoding=None):
    """Encode a string to the file system encoding of our host.

    Does nothing if string cannot be encoded.

    :param str string: String to encode.
    :param str encoding: Override encoding to use instead of native file system
        encoding.
    :returns: Encoded string.
    :rtype: str
    """
    if not encoding:
        encoding = sys.getfilesystemencoding()
    if hasattr(string, "encode"):
        return string.encode(encoding)
    return string
pyfmodex/enums.py
class TIMEUNIT(Enum):
    """Time types used for position or length."""

    MS = 0x00000001  #: Milliseconds.

    #: PCM samples, related to milliseconds * samplerate / 1000.
    PCM = 0x00000002

    #: Bytes, related to PCM samples * channels * datawidth (ie 16bit = 2
    #: bytes).
    PCMBYTES = 0x00000004

    #: Raw file bytes of (compressed) sound data (does not include headers).
    #: Only used by :py:meth:`~pyfmodex.sound.Sound.get_length` and
    #: :py:meth:`~pyfmodex.channel.Channel.get_position`.
    RAWBYTES = 0x00000008

    #: Fractions of one PCM sample. Unsigned int range 0 to 0xFFFFFFFF. Used for
    #: sub-sample granularity for :py:class:`~pyfmodex.dsp.DSP` purposes.
    PCMFRACTION = 0x00000010

    #: MOD/S3M/XM/IT. Order in a sequenced module format. Use
    #: :py:attr:`~pyfmodex.sound.Sound.format` to determine the PCM format
    #: being decoded to.
    MODORDER = 0x00000100

    #: MOD/S3M/XM/IT. Current row in a sequenced module format. Cannot use with
    #: :py:meth:`~pyfmodex.channel.Channel.set_position`.
    #: :py:meth:`~pyfmodex.sound.Sound.get_length` will return the number of
    #: rows in the currently playing or seeked to pattern.
    MODROW = 0x00000200

    #: MOD/S3M/XM/IT. Current pattern in a sequenced module format. Cannot use
    #: with :py:meth:`~pyfmodex.channel.Channel.set_position`.
    #: :py:meth:`~pyfmodex.sound.Sound.get_length` will return the number of
    #: patterns in the song and
    #: :py:meth:`~pyfmodex.channel.Channel.get_position` will return the
    #: currently playing pattern.
    MODPATTERN = 0x00000400
pyfmodex/utils.py
def check_type(obj, cls, msg="Bad type of passed argument (%s), expected %s"):
    """Verify if a given object is of a given type.

    :param object obj: Object to verify.
    :param class cls: Class to verify against.
    :param str msg: Error message when TypeError is raised.
    :raises TypeError: when object is not an instance of the expected class.
    """
    if not isinstance(obj, cls):
        raise TypeError(msg % (str(type(obj)), str(cls)))
pyfmodex/cone_settings.py
class ConeSettings:
    """Convenience wrapper class to handle 3D cone shape settings for simulated
    occlusion which is based on direction.
    """

    def __init__(self, sptr, class_name):
        """Constructor.

        Creates ConeSettings for an FMOD object.

        Usually not called directly, but through the
        `cone_settings` or `threed_cone_settings` property of an FMOD object.

        The :py:class:`~pyfmodex.flags.MODE` flag THREED must be set on this
        object otherwise :py:const:`~pyfmodex.enums.RESULT.NEEDS3D` is
        returned.

        When
        :py:meth:`~pyfmodex.channel_control.ChannelControl.cone_orientation` is
        set and a 3D 'cone' is set up, attenuation will automatically occur for
        a sound based on the relative angle of the direction the cone is
        facing, vs the angle between the sound and the listener.

        - If the relative angle is within the :py:attr:`inside_angle`, the
          sound will not have any attenuation applied.
        - If the relative angle is between the :py:attr:`inside_angle` and
          :py:attr:`outside_angle`, linear volume attenuation (between 1 and
          :py:attr:`outside_volume`) is applied between the two angles until it
          reaches the :py:attr:`outside_angle`.
        - If the relative angle is outside of the :py:attr:`outside_angle`
          the volume does not attenuate any further.

        :param sptr: pointer of the object having cone settings.
        :param class_name: class of the object having cone settings (Channel or
            ChannelGroup)
        """
        self._sptr = sptr
        self._in = c_float()
        self._out = c_float()
        self._outvol = c_float()
        self._get_func = "FMOD_%s_Get3DConeSettings" % class_name
        self._set_func = "FMOD_%s_Set3DConeSettings" % class_name
        ckresult(
            getattr(_dll, self._get_func)(
                self._sptr, byref(self._in), byref(self._out), byref(self._outvol)
            )
        )

    @property
    def inside_angle(self):
        """Inside cone angle.

        This is the angle spread within which the sound is unattenuated.
        Between 0 and 360.

        :type: int
        """
        return self._in.value

    @inside_angle.setter
    def inside_angle(self, angle):
        self._in = c_float(angle)
        self._commit()

    @property
    def outside_angle(self):
        """Outside cone angle.

        This is the angle spread outside of which the sound is attenuated to
        its :py:attr:`outside_volume`. Between 0 and 360.

        :type: int
        """
        return self._out.value

    @outside_angle.setter
    def outside_angle(self, angle):
        self._out = c_float(angle)
        self._commit()

    @property
    def outside_volume(self):
        """Cone outside volume.

        Between 0 and 1.

        :type: float
        """
        return self._outvol.value

    @outside_volume.setter
    def outside_volume(self, vol):
        self._outvol = c_float(vol)
        self._commit()

    def _commit(self):
        """Apply a changed code setting."""
        ckresult(
            getattr(_dll, self._set_func)(self._sptr, self._in, self._out, self._outvol)
        )
pyfmodex/enums.py
class SOUND_FORMAT(Enum):
    """These definitions describe the native format of the hardware or software
    buffer that will be used.
    """

    NONE = 0  #: Uninitalized / unknown.
    PCM8 = 1  #: 8bit integer PCM data.
    PCM16 = 2  #: 16bit integer PCM data.
    PCM24 = 3  #: 24bit integer PCM data.
    PCM32 = 4  #: 32bit integer PCM data.
    PCMFLOAT = 5  #: 32bit floating point PCM data.

    #: Sound data is in its native compressed format. See
    #: :py:class:`~pyfmodex.flags.MODE` flag CREATECOMPRESSEDSAMPLE.
    BITSTREAM = 6

    MAX = 7  #: Maximum number of sound formats supported.
pyfmodex/structures.py
class VECTOR(Structure):
    """Structure describing a point in 3D space.

    FMOD uses a left handed coordinate system by default. To use a right handed
    coordinate system specify
    :py:attr:`~pyfmodex.flags.INIT_FLAGS.THREED_RIGHTHANDED` in
    :py:meth:`~pyfmodex.system.System.init`.

    :ivar float x: X coordinate in 3D space.
    :ivar float y: Y coordinate in 3D space.
    :ivar float z: Z coordinate in 3D space.
    """

    _fields_ = [("x", c_float), ("y", c_float), ("z", c_float)]

    @staticmethod
    def from_list(lst):
        """Instantiate a VECTOR from a list with three coordinate floats."""
        vec = VECTOR()
        vec.x = lst[0]
        vec.y = lst[1]
        vec.z = lst[2]
        return vec

    def to_list(self):
        """The VECTOR as a list of three coordinate floats."""
        return [self.x, self.y, self.z]
pyfmodex/flags.py
class MODE(Flag):
    """Sound description bitfields, bitwise OR them together for loading and
    describing sounds.

    By default a sound will open as a static sound that is decompressed fully
    into memory to PCM (i.e. equivalent of :py:attr:`CREATESAMPLE`). To have a
    sound stream instead, use :py:attr:`CREATESTREAM`, or use the wrapper
    function :py:meth:`~pyfmodex.system.System.create_stream`.

    Some opening modes (i.e. :py:attr:`OPENUSER`, :py:attr:`OPENMEMORY`,
    :py:attr:`OPENMEMORY_POINT`, :py:attr:`OPENRAW`) will need extra
    information. This can be provided using the
    :py:class:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO` structure.

    Specifying :py:attr:`OPENMEMORY_POINT` will POINT to your memory rather
    allocating its own sound buffers and duplicating it internally. This means
    you cannot free the memory while FMOD is using it, until after
    :py:meth:`~pyfmodex.sound.Sound.release` is called.

    With :py:attr:`OPENMEMORY_POINT`, for PCM formats, only WAV, FSB, and RAW
    are supported. For compressed formats, only those formats supported by
    :py:attr:`CREATECOMPRESSEDSAMPLE` are supported.

    With :py:attr:`OPENMEMORY_POINT` and :py:attr:`OPENRAW` or PCM, if using
    them together, note that you must pad the data on each side by 16 bytes.
    This is so fmod can modify the ends of the data for looping / interpolation
    / mixing purposes. If a wav file, you will need to insert silence, and then
    reset loop points to stop the playback from playing that silence.
    """

    #: Default for all modes listed below: :py:attr:`LOOP_OFF`,
    #: :py:attr:`TWOD`, :py:attr:`THREED_WORLDRELATIVE`,
    #: :py:attr:`THREED_INVERSEROLLOFF`.
    DEFAULT = 0x0

    #: For non looping sounds. (DEFAULT). Overrides :py:attr:`LOOP_NORMAL` /
    #: :py:attr:`LOOP_BIDI`.
    LOOP_OFF = 0x00000001

    #: For forward looping sounds.
    LOOP_NORMAL = 0x00000002

    #: For bidirectional looping sounds. (only works on software mixed static
    # sounds).
    LOOP_BIDI = 0x00000004

    #: Ignores any 3D processing. (DEFAULT).
    TWOD = 0x00000008

    #: Makes the sound positionable in 3D. Overrides :py:attr:`TWOD`.
    THREED = 0x00000010

    #: Decompress at runtime, streaming from the source provided (i.e. from
    #: disk). Overrides :py:attr:`CREATESAMPLE` and
    #: :py:attr:`CREATECOMPRESSEDSAMPLE`. Note a stream can only be played once
    #: at a time due to a stream only having one stream buffer and file handle.
    #: Open multiple streams to have them play concurrently.
    CREATESTREAM = 0x00000080

    #: Decompress at loadtime, decompressing or decoding whole file into memory
    #: as the target sample format (ie PCM). Fastest for playback and most
    #: flexible.
    CREATESAMPLE = 0x00000100

    #: Load MP2/MP3/FADPCM/IMAADPCM/Vorbis/AT9 or XMA into memory and leave it
    #: compressed. Vorbis/AT9/FADPCM encoding only supported in the .FSB
    #: container format. During playback the FMOD software mixer will decode it
    #: in realtime as a 'compressed sample'. Overrides :py:attr:`CREATESAMPLE`.
    #: If the sound data is not one of the supported formats, it will behave as
    #: if it was created with :py:attr:`CREATESAMPLE` and decode the sound into
    #: PCM.
    CREATECOMPRESSEDSAMPLE = 0x00000200

    #: Opens a user created static sample or stream. Use the
    #: :py:class:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO` structure
    #: to specify format, defaultfrequency, numchannels, and optionally a read
    #: callback. If a user created 'sample' is created with no read callback,
    #: the sample will be empty. Use :py:meth:`~pyfmodex.sound.Sound.lock` and
    #: :py:meth:`~pyfmodex.sound.Sound.unlock` to place sound data into the
    #: sound if this is the case.
    OPENUSER = 0x00000400

    #: "name_or_data" will be interpreted as a pointer to memory instead of
    #: filename for creating sounds. Use the
    #: :py:class:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO` structure
    #: to specify length. If used with :py:attr:`CREATESAMPLE` or
    #: :py:attr:`CREATECOMPRESSEDSAMPLE`, FMOD duplicates the memory into its
    #: own buffers. Your own buffer can be freed after open, unless you are
    #: using :py:attr:`NONBLOCKING`. Then, wait until the Sound is in the
    #: :py:attr:`OPENSTATE_READY` state. If used with :py:attr:`CREATESTREAM`,
    #: FMOD will stream out of the buffer whose pointer you passed in. In this
    #: case, your own buffer should not be freed until you have finished with
    #: and released the stream.
    OPENMEMORY = 0x00000800

    #: "name_or_data" will be interpreted as a pointer to memory instead of
    #: filename for creating sounds. Use the
    #: :py:class:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO` structure
    #: to specify length. This differs to :py:attr:`OPENMEMORY` in that it uses
    #: the memory as is, without duplicating the memory into its own buffers.
    #: Cannot be freed after open, only after Sound::release. Will not work if
    #: the data is compressed and :py:attr:`CREATECOMPRESSEDSAMPLE` is not
    #: used. Cannot be used in conjunction with
    #: :py:attr:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO.encryptionkey`.
    OPENMEMORY_POINT = 0x10000000

    #: Will ignore file format and treat as raw pcm. Use the
    #: :py:class:`~pyfmodex.structure_declarations.CREATESOUNDEXINFO` structure
    #: to specify format. Requires at least defaultfrequency, numchannels and
    #: format to be specified before it will open. Must be little endian data.
    OPENRAW = 0x00001000

    #: Just open the file, don't prebuffer or read. Good for fast opens for
    #: info, or when :py:meth:`~pyfmodex.sound.Sound.read_data` is to be used.
    OPENONLY = 0x00002000

    #: For :py:meth:`~pyfmodex.system.System.create_sound` - for accurate
    #: :py:meth:`~pyfmodex.sound.Sound.get_length` /
    #: :py:meth:`~pyfmodex.channel.Channel.set_position` on VBR MP3, and
    #: MOD/S3M/XM/IT/MIDI files. Scans file first, so takes longer to open.
    #: :py:attr:`OPENONLY` does not affect this.
    ACCURATETIME = 0x00004000

    #: For corrupted / bad MP3 files. This will search all the way through the
    #: file until it hits a valid MPEG header. Normally only searches for 4K.
    MPEGSEARCH = 0x00008000

    #: For opening sounds and getting streamed subsounds (seeking)
    #: asynchronously. Use :py:attr:`~pyfmodex.sound.Sound.open_state` to poll
    #: the state of the sound as it opens or retrieves the subsound in the
    #: background.
    NONBLOCKING = 0x00010000

    #: Unique sound, can only be played one at a time
    UNIQUE = 0x00020000

    #: Make the sound's position, velocity and orientation relative to the
    #: listener.
    THREED_HEADRELATIVE = 0x00040000

    #: Make the sound position, velocity and orientation absolute (relative
    #: to the world). (DEFAULT)
    THREED_WORLDRELATIVE = 0x00080000

    #: This sound will follow the inverse rolloff model where mindistance =
    #: full volume, maxdistance = where sound stops attenuating, and rolloff is
    #: fixed according to the global rolloff factor. (DEFAULT)
    THREED_INVERSEROLLOFF = 0x00100000

    #: This sound will follow a linear rolloff model where mindistance = full
    #: volume, maxdistance = silence.
    THREED_LINEARROLLOFF = 0x00200000

    #: This sound will follow a linear-square rolloff model where mindistance =
    #: full volume, maxdistance = silence.
    THREED_LINEARSQUAREROLLOFF = 0x00400000

    #: This sound will follow the inverse rolloff model at distances close to
    #: mindistance and a linear-square rolloff close to maxdistance.
    THREED_INVERSETAPEREDROLLOFF = 0x00800000

    #: This sound will follow a rolloff model defined by
    #: :py:attr:`~pyfmodex.sound.Sound.custom_rolloff`.
    THREED_CUSTOMROLLOFF = 0x04000000

    #: This sonds is not be affected by geometry occlusion. If not specified in
    #: :py:attr:`~pyfmodex.sound.Sound.mode`, or
    #: :py:attr:`~pyfmodex.channel_control.ChannelControl.mode`, the flag is
    #: cleared and it is affected by geometry again.
    THREED_IGNOREGEOMETRY = 0x40000000

    #: Skips id3v2/asf/etc tag checks when opening a sound, to reduce seek/read
    #: overhead when opening files.
    IGNORETAGS = 0x02000000

    #: Removes some features from samples to give a lower memory overhead, like
    #: :py:attr:`~pyfmodex.sound.Sound.name`.
    LOWMEM = 0x08000000

    #: For sounds that start virtual (due to being quiet or low importance),
    #: instead of swapping back to audible, and playing at the correct offset
    #: according to time, this flag makes the sound play from the start.
    VIRTUAL_PLAYFROMSTART = 0x80000000
pyfmodex/enums.py
class SOUND_TYPE(Enum):
    """These definitions describe the type of song being played."""

    UNKNOWN = 0  #: 3rd party / unknown plugin format.
    AIFF = 1  #: AIFF.
    ASF = 2  #: Microsoft Advanced Systems Format (i.e. WMA/ASF/WMV).
    DLS = 3  #: Sound font / downloadable sound bank.
    FLAC = 4  #: FLAC lossless codec.
    FSB = 5  #: FMOD Sample Bank.
    IT = 6  #: Impulse Tracker.
    MIDI = 7  #: MIDI.
    MOD = 8  #: Protracker / Fasttracker MOD.
    MPEG = 9  #: MP2/MP3 MPEG.
    OGGVORBIS = 10  #: Ogg vorbis.
    PLAYLIST = 11  #: Information only from ASX/PLS/M3U/WAX playlists.
    RAW = 12  #: Raw PCM data.
    S3M = 13  #: ScreamTracker 3.
    USER = 14  #: User created sound.
    WAV = 15  #: Microsoft WAV.
    XM = 16  #: FastTracker 2 XM.
    XMA = 17  #: Xbox One XMA
    AUDIOQUEUE = 18  #: iPhone hardware decoder, supports AAC, ALAC and MP3.
    AT9 = 19  #: PS4 ATRAC 9 format.
    VORBIS = 20  #: Vorbis.
    MEDIA_FOUNDATION = 21  #: Windows Store Application built in system codecs.
    MEDIACODEC = 22  #: Android MediaCodec
    FADPCM = 23  #: FMOD Adaptive Differential Pulse Code Modulation.
    OPUS = 24  #: Opus.
    MAX = 25  #: Maximum number of sound types supported.
pyfmodex/structobject.py
class Structobject:
    """A 'bag' with keyword initialization, dict-semantics emulation and key
    iteration.
    """

    def __init__(self, **kw):
        """Initialize, and set attributes from all keyword arguments."""
        self.__members = []
        for k in list(kw.keys()):
            setattr(self, k, kw[k])
            self.__remember(k)

    def __remember(self, k):
        """Add k to the list of explicitly set values."""
        if k not in self.__members:
            self.__members.append(k)

    def __getitem__(self, key):
        """Equivalent of dict access by key."""
        try:
            return getattr(self, key)
        except AttributeError as attrerr:
            raise KeyError(key) from attrerr

    def __setitem__(self, key, value):
        setattr(self, key, value)
        self.__remember(key)

    def has_key(self, key):
        """wheter this Structobject contains a value for the given key.

        :rtype: bool
        """
        return hasattr(self, key)

    def keys(self):
        """All keys this Structobject has values for.

        :rtype: list
        """
        return self.__members

    def iterkeys(self):
        """All keys this Structobject has values for.

        :rtype: list
        """
        return self.__members

    def __iter__(self):
        return iter(self.__members)

    def __str__(self):
        """Describe those attributes explicitly set."""
        string = ""
        for member in self.__members:
            value = getattr(self, member)
            if string:
                string += ", "
            string += "%string: %string" % (member, repr(value))
        return string
pyfmodex/globalvars.py
def get_class(classname):
    """Get the class object from the given string.

    :param str classname: Class name.
    :rtype: class
    """
    return class_list[classname]
pyfmodex/structures.py
class TAG(Structure):
    """Tag data / metadata description.

    :ivar TAGTYPE type: Tag type.
    :ivar TAGDATATYPE datatype: Tag data type.
    :ivar str name: Name.
    :ivar data: Tag data.
    :ivar int datalen: Size of data.
    :ivar bool updated: True if this tag has been updated since last being
        accessed with :py:meth:`~pyfmodex.sound.Sound.get_tag`.
    """

    _fields_ = [
        ("type", c_int),
        ("datatype", c_int),
        ("name", c_char_p),
        ("data", c_void_p),
        ("datalen", c_uint),
        ("updated", c_bool),
    ]
pyfmodex/fmodobject.py
class FmodObject(object):
    def __init__(self, ptr):
    def _call_fmod(self, funcname, *args):
    def __eq__(self, other):
pyfmodex/utils.py
def ckresult(result):
    """Check if the result of our C API call is OK.

    :param RESULT result: C API call return code.
    :raises FmodError: when return code from C API call is not OK.
    """
    result = RESULT(result)
    if result is not RESULT.OK:
        raise FmodError(result)
pyfmodex/enums.py
class OPENSTATE(Enum):
    """These values describe what state a sound is in after the
    :py:class:`~pyfmodex.flags.MODE` flag NONBLOCKING has been used to open it.

    With streams, if you are using the :py:class:`~pyfmodex.flags.MODE` flag
    NONBLOCKING, note that if the user calls
    :py:meth:`~pyfmodex.sound.Sound.get_subsound`, a stream will go into
    :py:attr:`SEEKING` state and sound related commands will raise
    :py:exc:`~pyfmodex.exceptions.FmodError` with code
    :py:attr:`~RESULT.NOTREADY`.

    With streams, if you are using the :py:class:`~pyfmodex.flags.MODE` flag
    NONBLOCKING, note that if the user calls
    :py:meth:`~pyfmodex.channel.Channel.get_position`, a stream will go into
    :py:attr:`SETPOSITION` state and sound related commands will raise
    :py:exc:`~pyfmodex.exceptions.FmodError` with code
    :py:attr:`~RESULT.NOTREADY`.
    """

    READY = 0  #: Opened and ready to play.
    LOADING = 1  #: Initial load in progress.

    #: Failed to open - file not found, out of memory etc. See
    #: :py:exc:`~pyfmodex.exceptions.FmodError` code from
    #: :py:attr:`~pyfmodex.sound.Sound.open_state` for what happened.
    ERROR = 2

    CONNECTING = 3  #: Connecting to remote host (Internet sounds only).
    BUFFERING = 4  #: Buffering data.
    SEEKING = 5  #: Seeking to subsound and re-flushing stream buffer.

    #: Ready and playing, but not possible to release at this time without
    #: stalling the main thread.
    PLAYING = 6

    SETPOSITION = 7  #: Seeking within a stream to a different position.
    MAX = 8  #: Maximum number of open state types.


Question: from ctypes import *
from .cone_settings import ConeSettings
from .enums import OPENSTATE, SOUND_FORMAT, SOUND_TYPE, TIMEUNIT
from .flags import MODE
from .fmodobject import FmodObject, _dll
from .globalvars import get_class
from .structobject import Structobject as so
from .structures import TAG, VECTOR
from .utils import check_type, ckresult, prepare_str
        the sound becomes :py:attr:`~pyfmodex.enums.OPENSTATE.READY` or
        :py:attr:`~pyfmodex.enums.OPENSTATE.ERROR`.

        :param int index: Index of the subsound.
        :rtype: Sound
        """
        sh_ptr = c_void_p()
        self._call_fmod("FMOD_Sound_GetSubSound", index, byref(sh_ptr))
        return Sound(sh_ptr)

    @property
    def subsound_parent(self):
        """The parent Sound object that contains this subsound.

        None if this sound is not a subsound.

        :type: Sound
        """
        sh_ptr = c_void_p()
        self._call_fmod("FMOD_Sound_GetSubSoundParent", byref(sh_ptr))
        return Sound(sh_ptr)

    def get_sync_point(self, index):
        """Retrieve a sync point.

        :param int index: Index of the sync point.
        :rtype: Sync point.
        """
        syncpoint = c_void_p()
        self._call_fmod("FMOD_Sound_GetSyncPoint", index, byref(syncpoint))
        return syncpoint.value

    def get_sync_point_info(self, point, offset_type):
        """Retrieve information on an embedded sync point.

        :param point: Sync point.
        :param offset_type: The unit in which the point's offset should be expressed.
        :rtype: Structobject with the following members:

            - name: Name of the syncpoint (str)
            - offset: Offset of the syncpoint, expressed in the given offset_type (int)
        """
        name = create_string_buffer(256)
        offset = c_uint()
        self._call_fmod(
            "FMOD_Sound_GetSyncPointInfo",
            c_void_p(point),
            byref(name),
            256,
            byref(offset),
            offset_type.value,
        )
        return so(
            name=name.value, offset=offset.value
        )

    @property
    def system_object(self):
        """The parent System object.

        :type: System
        """
        sptr = c_void_p()
        self._call_fmod("FMOD_Sound_GetSystemObject", byref(sptr))
        return get_class("System")(sptr)

    def play(self, channel_group=None, paused=False):
        """Instruct the parent System object to play the sound.

        See :py:meth:`~pyfmodex.system.System.play_sound`.

        :param ChannelGroup channel_group: Group to output to instead of the
            master.
        :param bool paused: Whether to start in the paused state.
        :returns: Newly playing channel.
        :rtype: Channel
        """
        return self.system_object.play_sound(self, channel_group, paused)

    def get_tag(self, index, name=None):
        """Retrieve a metadata tag.

        'Tags' are metadata stored within a sound file. These can be things
        like a song's name, composer etc...

        The number of tags available can be found with :py:attr:`num_tags`.

        The way to display or retrieve tags can be done in three different ways:

            - All tags can be continuously retrieved by looping from 0 to the
              numtags value in :py:attr:`num_tags` - 1. Updated tags will
              refresh automatically, and the 'updated' member of the
              :py:class:`~pyfmodex.structures.TAG` structure will be set to
              True if a tag has been updated, due to something like a netstream
              changing the song name for example.

            - Tags can be retrieved by specifying -1 as the index and only
              updating tags that are returned. If all tags are retrieved and
              this method is called it will raise an
              :py:exc:`~pyfmodex.exceptions.FmodError` with code
              :py:attr:`~pyfmodex.enums.RESULT.TAGNOTFOUND`.

            - Specific tags can be retrieved by specifying a name parameter.
              The index can be 0 based or -1 in the same fashion as described
              previously.

        Note with netstreams an important consideration must be made between
        songs, a tag may occur that changes the playback rate of the song. It
        is up to the user to catch this and reset the playback rate with
        :py:attr:`~pyfmodex.channel.Channel.frequency`.

        A sample rate change will be signalled with a tag of type
        :py:attr:`~pyfmodex.enums.TAGTYPE.FMOD`.

        :param int index: Index into the tag list as restricted by name.
        :param str name: Name of a type of tag to retrieve. Specify None to
            retrieve all types of tags.
        """
        name = prepare_str(name, "ascii")
        tag = TAG()

Answer:         ckresult(_dll.FMOD_Sound_GetTag(self._ptr, name, index, byref(tag)))