code:
//////////////////////////////////////////////////////////////////////////////
//
//  HEADER BEGINS HERE
//

#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H
#define STB_VORBIS_INCLUDE_STB_VORBIS_H

#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)
#define STB_VORBIS_NO_STDIO 1
#endif

#ifndef STB_VORBIS_NO_STDIO
#include <stdio.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

///////////   THREAD SAFETY

// Individual stb_vorbis* handles are not thread-safe; you cannot decode from
// them from multiple threads at the same time. However, you can have multiple
// stb_vorbis* handles and decode from them independently in multiple thrads.

///////////   MEMORY ALLOCATION

// normally stb_vorbis uses malloc() to allocate memory at startup,
// and alloca() to allocate temporary memory during a frame on the
// stack. (Memory consumption will depend on the amount of setup
// data in the file and how you set the compile flags for speed
// vs. size. In my test files the maximal-size usage is ~150KB.)
//
// You can modify the wrapper functions in the source (setup_malloc,
// setup_temp_malloc, temp_malloc) to change this behavior, or you
// can use a simpler allocation model: you pass in a buffer from
// which stb_vorbis will allocate _all_ its memory (including the
// temp memory). "open" may fail with a VORBIS_outofmem if you
// do not pass in enough data; there is no way to determine how
// much you do need except to succeed (at which point you can
// query get_info to find the exact amount required. yes I know
// this is lame).
//
// If you pass in a non-NULL buffer of the type below, allocation
// will occur from it as described above. Otherwise just pass NULL
// to use malloc()/alloca()

typedef struct
{
   char *alloc_buffer;
   int   alloc_buffer_length_in_bytes;
} stb_vorbis_alloc;

///////////   FUNCTIONS USEABLE WITH ALL INPUT MODES

typedef struct stb_vorbis stb_vorbis;

typedef struct
{
   unsigned int sample_rate;
   int channels;

   unsigned int setup_memory_required;
   unsigned int setup_temp_memory_required;
   unsigned int temp_memory_required;

   int max_frame_size;
} stb_vorbis_info;

// get general information about the file
extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f);

// get the last error detected (clears it, too)
extern int stb_vorbis_get_error(stb_vorbis *f);

// close an ogg vorbis file and free all memory in use
extern void stb_vorbis_close(stb_vorbis *f);

// this function returns the offset (in samples) from the beginning of the
// file that will be returned by the next decode, if it is known, or -1
// otherwise. after a flush_pushdata() call, this may take a while before
// it becomes valid again.
// NOT WORKING YET after a seek with PULLDATA API
extern int stb_vorbis_get_sample_offset(stb_vorbis *f);

// returns the current seek point within the file, or offset from the beginning
// of the memory buffer. In pushdata mode it returns 0.
extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f);

///////////   PUSHDATA API

#ifndef STB_VORBIS_NO_PUSHDATA_API

// this API allows you to get blocks of data from any source and hand
// them to stb_vorbis. you have to buffer them; stb_vorbis will tell
// you how much it used, and you have to give it the rest next time;
// and stb_vorbis may not have enough data to work with and you will
// need to give it the same data again PLUS more. Note that the Vorbis
// specification does not bound the size of an individual frame.

extern stb_vorbis *stb_vorbis_open_pushdata(
         unsigned char *datablock, int datablock_length_in_bytes,
         int *datablock_memory_consumed_in_bytes,
         int *error,
         stb_vorbis_alloc *alloc_buffer);
// create a vorbis decoder by passing in the initial data block containing
//    the ogg&vorbis headers (you don't need to do parse them, just provide
//    the first N bytes of the file--you're told if it's not enough, see below)
// on success, returns an stb_vorbis *, does not set error, returns the amount of
//    data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;
// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed
// if returns NULL and *error is VORBIS_need_more_data, then the input block was
//       incomplete and you need to pass in a larger block from the start of the file

extern int stb_vorbis_decode_frame_pushdata(
         stb_vorbis *f, unsigned char *datablock, int datablock_length_in_bytes,
         int *channels,             // place to write number of float * buffers
         float ***output,           // place to write float ** array of float * buffers
         int *samples               // place to write number of output samples
     );
// decode a frame of audio sample data if possible from the passed-in data block
//
// return value: number of bytes we used from datablock
// possible cases:
//     0 bytes used, 0 samples output (need more data)
//     N bytes used, 0 samples output (resynching the stream, keep going)
//     N bytes used, M samples output (one frame of data)
// note that after opening a file, you will ALWAYS get one N-bytes,0-sample
// frame, because Vorbis always "discards" the first frame.
//
// Note that on resynch, stb_vorbis will rarely consume all of the buffer,
// instead only datablock_length_in_bytes-3 or less. This is because it wants
// to avoid missing parts of a page header if they cross a datablock boundary,
// without writing state-machiney code to record a partial detection.
//
// The number of channels returned are stored in *channels (which can be
// NULL--it is always the same as the number of channels reported by
// get_info). *output will contain an array of float* buffers, one per
// channel. In other words, (*output)[0][0] contains the first sample from
// the first channel, and (*output)[1][0] contains the first sample from
// the second channel.

extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
// inform stb_vorbis that your next datablock will not be contiguous with
// previous ones (e.g. you've seeked in the data); future attempts to decode
// frames will cause stb_vorbis to resynchronize (as noted above), and
// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it
// will begin decoding the _next_ frame.
//
// if you want to seek using pushdata, you need to seek in your file, then
// call stb_vorbis_flush_pushdata(), then start calling decoding, then once
// decoding is returning you data, call stb_vorbis_get_sample_offset, and
// if you don't like the result, seek your file again and repeat.
#endif

//////////   PULLING INPUT API

#ifndef STB_VORBIS_NO_PULLDATA_API
// This API assumes stb_vorbis is allowed to pull data from a source--
// either a block of memory containing the _entire_ vorbis stream, or a
// FILE * that you or it create, or possibly some other reading mechanism
// if you go modify the source to replace the FILE * case with some kind
// of callback to your code. (But if you don't support seeking, you may
// just want to go ahead and use pushdata.)

#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
extern int stb_vorbis_decode_filename(char *filename, int *channels, short **output);
#endif
extern int stb_vorbis_decode_memory(unsigned char *mem, int len, int *channels, short **output);
// decode an entire file and output the data interleaved into a malloc()ed
// buffer stored in *output. The return value is the number of samples
// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.
// When you're done with it, just free() the pointer returned in *output.

extern stb_vorbis * stb_vorbis_open_memory(unsigned char *data, int len,
                                  int *error, stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an ogg vorbis stream in memory (note
// this must be the entire stream!). on failure, returns NULL and sets *error

#ifndef STB_VORBIS_NO_STDIO
extern stb_vorbis * stb_vorbis_open_filename(char *filename,
                                  int *error, stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from a filename via fopen(). on failure,
// returns NULL and sets *error (possibly to VORBIS_file_open_failure).

extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close,
                                  int *error, stb_vorbis_alloc *alloc_buffer);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell). on failure, returns NULL and sets *error.
// note that stb_vorbis must "own" this stream; if you seek it in between
// calls to stb_vorbis, it will become confused. Morever, if you attempt to
// perform stb_vorbis_seek_*() operations on this file, it will assume it
// owns the _entire_ rest of the file after the start point. Use the next
// function, stb_vorbis_open_file_section(), to limit it.

extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close,
                int *error, stb_vorbis_alloc *alloc_buffer, unsigned int len);
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
// the _current_ seek point (ftell); the stream will be of length 'len' bytes.
// on failure, returns NULL and sets *error. note that stb_vorbis must "own"
// this stream; if you seek it in between calls to stb_vorbis, it will become
// confused.
#endif

extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number);
extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number);
// NOT WORKING YET
// these functions seek in the Vorbis file to (approximately) 'sample_number'.
// after calling seek_frame(), the next call to get_frame_*() will include
// the specified sample. after calling stb_vorbis_seek(), the next call to
// stb_vorbis_get_samples_* will start with the specified sample. If you
// do not need to seek to EXACTLY the target sample when using get_samples_*,
// you can also use seek_frame().

extern void stb_vorbis_seek_start(stb_vorbis *f);
// this function is equivalent to stb_vorbis_seek(f,0), but it
// actually works

extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f);
extern float        stb_vorbis_stream_length_in_seconds(stb_vorbis *f);
// these functions return the total length of the vorbis stream

extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output);
// decode the next frame and return the number of samples. the number of
// channels returned are stored in *channels (which can be NULL--it is always
// the same as the number of channels reported by get_info). *output will
// contain an array of float* buffers, one per channel. These outputs will
// be overwritten on the next call to stb_vorbis_get_frame_*.
//
// You generally should not intermix calls to stb_vorbis_get_frame_*()
// and stb_vorbis_get_samples_*(), since the latter calls the former.

#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts);
extern int stb_vorbis_get_frame_short            (stb_vorbis *f, int num_c, short **buffer, int num_samples);
#endif
// decode the next frame and return the number of samples per channel. the
// data is coerced to the number of channels you request according to the
// channel coercion rules (see below). You must pass in the size of your
// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.
// The maximum buffer size needed can be gotten from get_info(); however,
// the Vorbis I specification implies an absolute maximum of 4096 samples
// per channel. Note that for interleaved data, you pass in the number of
// shorts (the size of your array), but the return value is the number of
// samples per channel, not the total number of samples.

// Channel coercion rules:
//    Let M be the number of channels requested, and N the number of channels present,
//    and Cn be the nth channel; let stereo L be the sum of all L and center channels,
//    and stereo R be the sum of all R and center channels (channel assignment from the
//    vorbis spec).
//        M    N       output
//        1    k      sum(Ck) for all k
//        2    *      stereo L, stereo R
//        k    l      k > l, the first l channels, then 0s
//        k    l      k <= l, the first k channels
//    Note that this is not _good_ surround etc. mixing at all! It's just so
//    you get something useful.

extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats);
extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples);
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.
// Returns the number of samples stored per channel; it may be less than requested
// at the end of the file. If there are no more samples in the file, returns 0.

#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts);
extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples);
#endif
// gets num_samples samples, not necessarily on a frame boundary--this requires
// buffering so you have to supply the buffers. Applies the coercion rules above
// to produce 'channels' channels. Returns the number of samples stored per channel;
// it may be less than requested at the end of the file. If there are no more
// samples in the file, returns 0.

#endif

////////   ERROR CODES

enum STBVorbisError
{
   VORBIS__no_error,

   VORBIS_need_more_data=1,             // not a real error

   VORBIS_invalid_api_mixing,           // can't mix API modes
   VORBIS_outofmem,                     // not enough memory
   VORBIS_feature_not_supported,        // uses floor 0
   VORBIS_too_many_channels,            // STB_VORBIS_MAX_CHANNELS is too small
   VORBIS_file_open_failure,            // fopen() failed
   VORBIS_seek_without_length,          // can't seek in unknown-length file

   VORBIS_unexpected_eof=10,            // file is truncated?
   VORBIS_seek_invalid,                 // seek past EOF

   // decoding errors (corrupt/invalid stream) -- you probably
   // don't care about the exact details of these

   // vorbis errors:
   VORBIS_invalid_setup=20,
   VORBIS_invalid_stream,

   // ogg errors:
   VORBIS_missing_capture_pattern=30,
   VORBIS_invalid_stream_structure_version,
   VORBIS_continued_packet_flag_invalid,
   VORBIS_incorrect_stream_serial_number,
   VORBIS_invalid_first_page,
   VORBIS_bad_packet_type,
   VORBIS_cant_find_last_page,
   VORBIS_seek_failed,
};

#ifdef __cplusplus
}
#endif

#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H
//
//  HEADER ENDS HERE
//
//////////////////////////////////////////////////////////////////////////////

license: mit


code:
/**
 * Copyright (c) 2011-2012 Nokia Corporation.
 * All rights reserved.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#ifndef TRACE_H
#define TRACE_H

#ifdef GE_DEBUG
    #include <QDebug>

    #define DEBUG_POINT qDebug() << __PRETTY_FUNCTION__ << ":" << __LINE__
    #define DEBUG_INFO(ARG...) \
        qDebug() << __PRETTY_FUNCTION__ << ":" << __LINE__ << ":" << ARG
#else
    #define DEBUG_POINT do {} while (0)
    #define DEBUG_INFO(...) do {} while (0)
#endif

#endif // TRACE_H

// End of file.


code:
/**
 * Copyright (c) 2011-2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#include "volumekeys.h"
#include "trace.h"

using namespace GE;

/*!
  \class VolumeKeys
  \brief Monitors the state of volume up and down buttons on Symbian devices.
*/

/*!
  Constructor.
*/
VolumeKeys::VolumeKeys(QObject *parent, IVolumeKeyObserver *observer) :
    QObject(parent),
    m_interfaceSelector(NULL),
    m_coreTarget(NULL),
    m_observer(observer)
{
    DEBUG_INFO(this);

    QT_TRAP_THROWING(
        m_interfaceSelector = CRemConInterfaceSelector::NewL();
        m_coreTarget = CRemConCoreApiTarget::NewL(*m_interfaceSelector, *this);
        m_interfaceSelector->OpenTargetL();
    );
}

/*!
  Destructor.
*/
VolumeKeys::~VolumeKeys()
{
    DEBUG_POINT;

    delete m_interfaceSelector;
    m_interfaceSelector = NULL;
    m_coreTarget = NULL; // owned by interfaceselector
}

/*!
  From MRemConCoreApiTargetObserver.

  Checks if volume up/down keys are pressed and calls observer's (GameWindow's)
  callbacks respectively.
*/
void VolumeKeys::MrccatoCommand(TRemConCoreApiOperationId operationId,
                                TRemConCoreApiButtonAction buttonAct)
{
    DEBUG_INFO("operation:" << operationId << " action:" << buttonAct);

    if (buttonAct == ERemConCoreApiButtonClick) {
        if (operationId == ERemConCoreApiVolumeUp)
            m_observer->onVolumeUp();
        else if (operationId == ERemConCoreApiVolumeDown)
            m_observer->onVolumeDown();
    }
}


code:
/**
 * Copyright (c) 2011-2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#ifndef VOLUMEKEYS_H
#define VOLUMEKEYS_H

#include <QtGui>
#include <remconcoreapitargetobserver.h>
#include <remconcoreapitarget.h>
#include <remconinterfaceselector.h>

namespace GE {

class IVolumeKeyObserver
{
public:
    virtual void onVolumeUp() = 0;
    virtual void onVolumeDown() = 0;
};

class VolumeKeys : public QObject,
                   public MRemConCoreApiTargetObserver
{
    Q_OBJECT

public:
    explicit VolumeKeys(QObject *parent, IVolumeKeyObserver *observer);
    ~VolumeKeys();

private: // from MRemConCoreApiTargetObserver
    void MrccatoCommand(TRemConCoreApiOperationId operationId,
        TRemConCoreApiButtonAction buttonAct);

private:
    CRemConInterfaceSelector *m_interfaceSelector;
    CRemConCoreApiTarget *m_coreTarget;
    IVolumeKeyObserver *m_observer;
};

} // namespace GE

#endif // VOLUMEKEYS_H


code:
/**
 * Copyright (c) 2011-2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#include <QtGlobal>
#include <stdlib.h>
#include <memory.h>
#include <QDebug>

#include "trace.h"
#include "vorbisdecoder.h"
#include "oggpage.h"
#include "oggconstants.h"

using namespace GE;

const int maxblocksize = 512 * 1024;

/*!
  Constructor. If cached is set to true the decoder loads the entire encoded
  file into memory.
*/
VorbisDecoder::VorbisDecoder(bool cached, QObject *parent) :
    QObject(parent),
    m_file(this),
    m_pageList(0),
    m_pos(0),
    m_cached(cached),
    m_cache(NULL),
    m_vorbis(NULL),
    m_decSamples(NULL),
    m_decSamplesLen(0),
    m_maxDecSamplesLen(0),
    m_readData(NULL),
    m_decodedLength(0)
{
}

/*!
  Destructor.
*/
VorbisDecoder::~VorbisDecoder()
{
    m_file.close();

    OggPage *p = m_pageList;
    while (p) {
        OggPage *pn = p->m_next;
        delete p;
        p = pn;
    }

    vorbisUninit();
    delete m_cache;
}

/*!
  Loads the given file, scans all the pages and initializes stb vorbis.
  Returns true if successful.
*/
bool VorbisDecoder::load(QString &filename)
{
    m_file.setFileName(filename);

    if (!m_file.open(QIODevice::ReadOnly)) {
        DEBUG_INFO("Failed to open file" << filename);
        return false;
    }

    if (m_cached) {
        m_length = m_file.size();
        m_pos = 0;
        m_cache = (unsigned char*)malloc(m_length);
        qint64 len = m_file.read((char*)m_cache, m_length);
        if (len != m_length) {
            DEBUG_INFO("Failed to read file" << filename << "read"
                <<  len << "expected" << m_length);
            return false;
        }
        m_file.close();
    }

    if (!scan())
        return false;

	if (vorbisInit() < 0) {
	    DEBUG_INFO("Failed to initialize vorbis decoder");
	    return false;
    }

    return true;
}

/*!
  Scans all ogg pages of the file in order to support seeking into arbitrary
  positions. Returns true if successful.
*/
bool VorbisDecoder::scan()
{
    // Read the first page. It must be the beginning-of-stream page.
    m_pageList = readPageHeader();
    if (!m_pageList || !m_pageList->m_headerType & OggBOS) {
        DEBUG_INFO("Failed to read BOS page");
        delete m_pageList;
        m_pageList = NULL;
        return false;
    }
    OggPage *page = m_pageList;

    // Read all the pages until eof or end-of-stream page reachead
    while (!(page->m_headerType & OggEOS) &&
            (page->m_next = readPageHeader()))
    {
        page->m_next->m_prev = page;
        page = page->m_next;
        //page->dump();
        seek(tell() + page->m_dataLength);
    }

    m_decodedLength = page->m_granulePosition;
    // TODO: fill this properly (the stream does not always start from 0)
    m_granuleOffset = 0;

    return true;
}

/*!
  Seek for an ogg page which contains (not really true, read forward) given
  PCM sample. samplePos is in units of PCM samples per channel. If vorbis
  packets span over multiple pages, the returned page is the last page. You'll
  have to seek backwards from the returned page in order to find the position
  where you should start decoding. Returns NULL if page was not found.
*/
const OggPage *VorbisDecoder::seekPage(quint64 samplePos)
{
    OggPage *p = m_pageList;
    if (!p)
        return NULL;

    do {
        if (p->m_granulePosition &&
            p->m_granulePosition - m_granuleOffset > samplePos) {
            return p;
        }
    } while ((p = p->m_next));

    return NULL;
}

/*!
  Returns the first decodable audio page. The beginning of an ogg file contains
  headers pages which are skipped.
*/
const OggPage *VorbisDecoder::firstAudioPage()
{
    OggPage *page = m_pageList;
    // 'The granule position of these first pages containing only headers is zero.'
    while (page->m_granulePosition == 0) {
        page = page->m_next;
        if (!page)
            return NULL;
    }
    return page;
}

/*!
  Returns info about the loaded file, such as channels and sample rate.
*/
const stb_vorbis_info *VorbisDecoder::fileInfo()
{
    return &m_info;
}

/*!
  Reads the header of an ogg page from current file position and returns
  filled instance of an OggPage. Returns NULL if something is wrong with the
  page.
*/
OggPage *VorbisDecoder::readPageHeader()
{
    int len;

    if (!m_file.isOpen() && !m_cache)
        return NULL;

    OggPage *page = new OggPage();
    if (!page) {
        DEBUG_INFO("Failed to allocate a page");
        return NULL;
    }

    page->m_pageStartPos = tell();

    len = read((unsigned char*)page, OggPageHeaderLen, -1);
    if (!len) {
        DEBUG_INFO("Failed to read a page");
        delete page;
        return NULL;
    }

    if (page->m_capturePattern != OggCapturePatternMagic) {
        DEBUG_INFO("Capture pattern mismatch");
        delete page;
        return NULL;
    }

    // First page does not contain segments at all. It contains only an Vorbis
    // identification header.
    if (page->m_headerType & OggBOS) {
        seek(OggFirstPageLen);
        page->m_segments = NULL;
        page->m_dataLength = 0;
        page->m_pageLength = OggFirstPageLen;
    } else {
        page->m_segments = (unsigned char*)malloc(page->m_pageSegments);
        if (!page->m_segments) {
            DEBUG_INFO("Failed to allocate page segments");
            delete page;
            return NULL;
        }
        len = read(page->m_segments, page->m_pageSegments, -1);
        if (len != page->m_pageSegments) {
            DEBUG_INFO("Read" << len << "segment lengths, expected"
                << page->m_pageSegments);
            delete page;
            return NULL;
        }

        for (int t = 0; t < page->m_pageSegments; t++) {
            page->m_dataLength += page->m_segments[t];
        }
    }
    page->m_dataStartPos = tell();
    page->m_pageLength = page->m_dataStartPos - page->m_pageStartPos +
        page->m_dataLength;

    return page;
}

int VorbisDecoder::read(unsigned char *buf, int len, int pos)
{
    if (m_cached) {
        if (!m_cache)
            return -1;
        if (pos >=0)
            seek(pos);
        int l = qMin(len, m_length - m_pos);
        memcpy(buf, &m_cache[m_pos], l);
        m_pos += l;
        return l;
    } else {
        if (!m_file.isOpen())
            return -1;
        if (pos >= 0) {
            if (!seek(pos))
                return -1;
        }
        return m_file.read((char*)buf, len);
    }
}

bool VorbisDecoder::seek(int pos)
{
    if (m_cached) {
        if (!m_cache)
            return false;
        if (pos >= m_length || pos < 0)
            return false;
        m_pos = pos;
        return m_pos;
    } else {
        if (!m_file.isOpen())
            return false;
        return m_file.seek(pos);
    }
}

int VorbisDecoder::tell()
{
    if (m_cached) {
        if (!m_cache)
            return -1;
        return m_pos;
    } else {
        if (!m_file.isOpen())
            return -1;
        return m_file.pos();
    }
}

/*!
  Reads the header pages from the beginning of loaded ogg file.
*/
unsigned char *VorbisDecoder::readHeaderPages(int *len)
{
    OggPage *page = m_pageList;
    if (!page)
        return NULL;

    *len = 0;
    // 'The granule position of these first pages containing only headers is zero.'
    while (page->m_granulePosition == 0) {
        *len += page->m_pageLength;
        page = page->m_next;
        if (!page)
            return NULL;
    }

    unsigned char *data = (unsigned char*)malloc(*len);
    if (!data)
        return NULL;

    int rlen = read(data, *len, 0);
    if (rlen != *len) {
        DEBUG_INFO("Read" << rlen << "bytes, expected" << *len);
        free(data);
        return NULL;
    }

    return data;
}

/*!
  Decode the entire file into memory.
*/
unsigned char *VorbisDecoder::decodeAll(unsigned int *len)
{
    int allocLen = m_decodedLength * sizeof(short) * m_info.channels;
    if (!m_vorbis)
        return NULL;

    unsigned char *outBuf = (unsigned char*)malloc(allocLen);
    if (!outBuf) {
        DEBUG_INFO("Failed to allocate" << allocLen << "bytes!");
        return NULL;
    }
    *len = 0;

    m_readPos = firstAudioPage()->m_pageStartPos;
    int rlen = 0;
    while ((rlen = read(m_readData, 65536 * 2, m_readPos)) > 0) {
        int leftover = 0;
        if (!vorbisDecode(m_readData, rlen, &leftover, false) ||
            !m_decSamplesLen) {
            DEBUG_INFO("vorbisDecode failed");
            return false;
        }
        m_readPos += rlen - leftover;

        if (*len + m_decSamplesLen > allocLen ) {
	        // Should not happen
            allocLen = m_decSamplesLen + *len;
            outBuf = (unsigned char*)realloc(outBuf, allocLen);
        	if (!outBuf) {
                DEBUG_INFO("Failed to allocate" << allocLen << "bytes!");
                return NULL;
            }
    	}
        memcpy(&outBuf[*len], m_decSamples, m_decSamplesLen);
        *len += m_decSamplesLen;
	}

	return outBuf;
}

/*!
  Return sample from given position. pos is in units of PCM samples.
*/
short VorbisDecoder::at(quint64 pos)
{
    if (!m_decSamplesLen || pos < m_decodedDataStart ||
        pos > m_decodedDataEnd) {
        if (!vorbisSeek(pos))
            return 0;
    }

    return m_decSamples[pos - m_decodedDataStart];
}

/*!
  Initializes stb vorbis.
*/
int VorbisDecoder::vorbisInit()
{
    int used = 0, error = 0;
    int len;

    unsigned char *data = readHeaderPages(&len);
    if (!data)
        return -1;

    m_vorbis = stb_vorbis_open_pushdata((unsigned char*)data, len, &used,
        &error, NULL);

    DEBUG_INFO("stb vorbis init complete, used" << used
        << "bytes, error" << error);

    if (!m_vorbis)
        return -error;

    memset(&m_info, 0, sizeof(stb_vorbis_info));
    m_info = stb_vorbis_get_info(m_vorbis);

    DEBUG_INFO("   sample_rate:" << m_info.sample_rate);
    DEBUG_INFO("      channels:" << m_info.channels);
    DEBUG_INFO("max_frame_size:" << m_info.max_frame_size);

	free(data);
	const OggPage *page = firstAudioPage();
	m_readPos = page->m_pageStartPos;
    m_readData = (unsigned char *)malloc(maxblocksize);

    return used;
}

/*!
  Frees up resources related to decoding.
*/
void VorbisDecoder::vorbisUninit()
{
    if (m_vorbis) {
        stb_vorbis_close(m_vorbis);
        m_vorbis = NULL;
    }

    free(m_readData);
    m_readData = NULL;
    free(m_decSamples);
    m_decSamples = NULL;
    m_decSamplesLen = 0;
}

/*!
  Decode a block of data. data/len defines the input data,
  m_decSamples/m_decSamplesLen contains the decoded data. leftover is the length
  of unused input data. If singleFrame is set to true this will decode only the
  first complete frame from the input data, otherwise this will try to decode
  all the complete frames.
*/
bool VorbisDecoder::vorbisDecode(unsigned char *data, int len, int *leftover,
                                 bool singleFrame)
{
    float **outputs;
    int channels, samplecount, t, i;
    int outPos = 0;
    unsigned char *p = data;

    m_decSamplesLen = 0;

	if (!m_decSamples) {
        m_maxDecSamplesLen = maxblocksize;
		m_decSamples = (short*)malloc(sizeof(short) * m_maxDecSamplesLen);
        if (!m_decSamples) {
            DEBUG_INFO("Failed to allocate" <<
                       sizeof(short) * m_maxDecSamplesLen << "bytes");
            return false;
        }
	}

    while (len) {
        int used = stb_vorbis_decode_frame_pushdata(m_vorbis, (unsigned char*)p,
            len, &channels, &outputs, &samplecount);
        if (!used && !samplecount)
            break;
        len -= used;
        p += used;
        if (outPos + samplecount * channels > m_maxDecSamplesLen) {
            m_maxDecSamplesLen += maxblocksize;
            m_decSamples = (short*)realloc(m_decSamples,
                m_maxDecSamplesLen * sizeof(short));
            if (!m_decSamples) {
                DEBUG_INFO("Failed to allocate" <<
                           sizeof(short) * m_maxDecSamplesLen << "bytes");
                return false;
            }
        }

        for (t = 0; t < samplecount; t++) {
            for (i = 0; i < channels; i++) {
                // TODO: Values are not clamped to -1...1 by stb vorbis
                m_decSamples[outPos++] = (short)(outputs[i][t] * 30000.0f);
            }
        }
        if (singleFrame && outPos)
            break;
    }
    *leftover = len;
    m_decSamplesLen = outPos * sizeof(short);
    return true;
}

/*!
  Informs stb vorbis that the the next datablock will not be contiguous with
  previous ones. This is needed when seeking. Decoding will continue from the
  beginning of next valid ogg page.
*/
bool VorbisDecoder::vorbisFlush()
{
	if (!m_vorbis)
		return false;
    stb_vorbis_flush_pushdata(m_vorbis);
    return true;
}

/*!
  Decode given page. Note that the given page defines the end of decoding,
  starting page is some of the previous pages.
*/
bool VorbisDecoder::vorbisDecodePage(const OggPage *page)
{
    const OggPage *startPage = page->m_prev;
    const OggPage *endPage = page;
    const OggPage *p = startPage;
    do {
        if (p->m_granulePosition != startPage->m_granulePosition &&
            p->m_granulePosition != (quint64)-1) {
            startPage = p;
            break;
        }
    } while((p = p->m_prev));

    m_readPos = startPage->m_pageStartPos;
    int lenToRead = endPage->m_pageStartPos - startPage->m_pageStartPos +
        endPage->m_pageLength;

    int rlen = lenToRead;
    int leftover = 0;
    if (m_cached) {
        if (!vorbisDecode(&m_cache[m_readPos], rlen, &leftover, false) ||
                !m_decSamplesLen) {
            DEBUG_INFO("vorbisDecode failed");
            return false;
        }
    } else {
        rlen = read(m_readData, lenToRead, m_readPos);
        if (!rlen)
            return false;

        if (!vorbisDecode(m_readData, rlen, &leftover, false) ||
                !m_decSamplesLen) {
            DEBUG_INFO("vorbisDecode failed");
            return false;
        }
    }
    m_readPos += rlen - leftover;

    unsigned int decPos = m_decSamplesLen / m_info.channels / sizeof(short);
    if (endPage->m_granulePosition < decPos) {
        m_decodedDataStart = 0;
    } else {
        m_decodedDataStart = (endPage->m_granulePosition - decPos -
            m_granuleOffset) * m_info.channels;
    }
    m_decodedDataEnd = (endPage->m_granulePosition - m_granuleOffset) *
            m_info.channels;
    return true;
}

/*!
  Decode next frame. This can be used only when decoding contiguously forward.
*/
bool VorbisDecoder::vorbisDecodeNext()
{
    int rlen = 65536;
    int leftover = 0;

    if (m_cached) {
        rlen = qMin(rlen, m_length - m_readPos);
        if (!vorbisDecode(&m_cache[m_readPos], rlen, &leftover, true) ||
                !m_decSamplesLen) {
            DEBUG_INFO("vorbisDecode failed");
            return false;
        }
    } else {
        rlen = read(m_readData, rlen, m_readPos);
        if (!rlen)
            return false;

        if (!vorbisDecode(m_readData, rlen, &leftover, true) ||
                !m_decSamplesLen) {
            DEBUG_INFO("vorbisDecode failed");
            return false;
        }
    }
    m_readPos += rlen - leftover;

    unsigned int decLen = m_decSamplesLen / sizeof(short);
    m_decodedDataStart = m_decodedDataEnd + 1;
    m_decodedDataEnd = m_decodedDataStart + decLen - 1;
    return true;
}

/*!
  Seek relatively from current sample position. offset is in units of PCM
  samples.
*/
bool VorbisDecoder::vorbisSeekRelative(qint64 offset)
{
    if (!m_vorbis)
        return false;

    int current = stb_vorbis_get_sample_offset(m_vorbis);
    if (current == -1) {
        DEBUG_INFO("no offset yet");
        return false;
    }

    if (m_decSamplesLen && current + offset - m_decodedDataEnd <
            m_info.max_frame_size) {
        if (!vorbisDecodeNext())
            return false;
    } else {
        const OggPage *page = seekPage((current + offset) / m_info.channels);
        if (!page)
            return false;

        if (!vorbisFlush())
            return false;

        if (!vorbisDecodePage(page))
            return false;
    }

    return true;
}

/*!
  Seek to given sample position. pos is in units of PCM samples.
*/
bool VorbisDecoder::vorbisSeek(qint64 pos)
{
    if (m_decSamplesLen && pos >= m_decodedDataStart &&
        pos < m_info.max_frame_size + m_decodedDataEnd) {
        if (!vorbisDecodeNext()) {
			// Stop on the first failure.
			m_decodedLength = m_decodedDataEnd;
            return false;
		}
    } else if (pos < m_decodedLength) {
        const OggPage *page;
		// Random position in the file
		page = seekPage(pos / m_info.channels);
		if (!page)
			return false;

		if (!vorbisFlush())
			return false;

		if (!vorbisDecodePage(page))
			return false;
    } else {
		return false;
	}

    return true;
}


code:
/**
 * Copyright (c) 2011-2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#ifndef GE_VORBISDECODER_H
#define GE_VORBISDECODER_H

#include <QFile>
#include "stb_vorbis.h"

namespace GE {

// Forward declarations (inside GE namespace)
class OggPage;

// Ogg vorbis decoder using stb vorbis in push mode
class VorbisDecoder : public QObject
{
    Q_OBJECT

public:
    explicit VorbisDecoder(bool cached = false, QObject *parent = NULL);
    virtual ~VorbisDecoder();

public:
    bool load(QString &filename);
    inline quint64 decodedLength() { return m_decodedLength; }
    unsigned char *decodeAll(unsigned int *len);
    const stb_vorbis_info *fileInfo();
    short at(quint64 pos);

private:
    bool scan();
    OggPage *readPageHeader();
    const OggPage *seekPage(quint64 samplePos);
    const OggPage *firstAudioPage();
    unsigned char *readHeaderPages(int *len);
    bool vorbisSeek(qint64 pos);
    bool vorbisSeekRelative(qint64 offset);
    bool vorbisDecodePage(const OggPage *page);
    bool vorbisDecodeNext();
    bool vorbisFlush();
    bool vorbisDecode(unsigned char *data, int len,
                      int *leftover, bool singleFrame);
    void vorbisUninit();
    int vorbisInit();
    int read(unsigned char *buf, int len, int pos);
    bool seek(int pos);
    int tell();

private:
    QFile m_file;
    OggPage *m_pageList;
    int m_pos;
    int m_length;
    bool m_cached;
    unsigned char *m_cache;
    quint64 m_decodedDataStart;
    quint64 m_decodedDataEnd;
    stb_vorbis_info m_info;
    stb_vorbis *m_vorbis;
    short *m_decSamples;
    int m_decSamplesLen;
	int m_maxDecSamplesLen;
    int m_readPos;
    unsigned char *m_readData;
    quint64 m_decodedLength;
    qint64 m_granuleOffset;
};

} // namespace GE

#endif // GE_VORBISDECODER_H


code:
/**
 * Copyright (c) 2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#include "vorbissource.h"
#include "audiobuffer.h"
#include "audioeffect.h"
#include "trace.h"
#include "vorbisdecoder.h"

#include <QDebug>
#include <QTime>

using namespace GE;

// Constants
const float GEMaxAudioSpeedValue(4096.0f);
const float GEDefaultAudioVolume(1.0f); // 1.0 => 100 %
const float GEDefaultAudioSpeed(1.0f); // 1.0 => 100 %

/*!
 * \class VorbisSource
 * \brief An AudioSource instance capable of playing Ogg Vorbis files.
 */

/*!
  Constructor.
*/
VorbisSource::VorbisSource(QString filename, QObject *parent)
    : AudioSource(parent),
      m_finished(true),
	  m_paused(false),
      m_destroyWhenFinished(false),
      m_fixedPos(0),
      m_fixedInc(0),
      m_fixedLeftVolume((int)GEMaxAudioVolumeValue),
      m_fixedRightVolume((int)GEMaxAudioVolumeValue),
      m_loopCount(0)
{
    DEBUG_POINT;
    m_decoder = new VorbisDecoder(true, this);
    m_decoder->load(filename);
}

/*!
  Destructor.
*/
VorbisSource::~VorbisSource()
{
    DEBUG_POINT;
    delete m_decoder;
}

/*!
  From AudioSource.

  The framework will use this to know whether this AudioSource can be
  destroyed or not.
*/
bool VorbisSource::canBeDestroyed()
{
    return false;
}

/*!
  From AudioSource.

  Returns an audio stream from the current sample.
*/
int VorbisSource::pullAudio(AUDIO_SAMPLE_TYPE *target, int bufferLength)
{
    if (m_paused || m_finished)
        return 0;

    unsigned int channelLength(m_decoder->decodedLength() - 2);
    unsigned int samplesToWrite(bufferLength / 2);
    unsigned int amount(0);
    unsigned int totalMixed(0);

    while (samplesToWrite > 0) {
        unsigned int samplesLeft;

        if (m_fixedInc == 0) {
            // No speed set. Will lead to division by zero error if not set.
            setSpeed(GEDefaultAudioSpeed);
        }

        samplesLeft = m_fixedInc > 0 ? channelLength - (m_fixedPos >> 12) :
            (m_fixedPos >> 12);

        // This is how much we can mix at least.
        unsigned int maxMixAmount = (int)(((long long int)(samplesLeft) << 12) /
                                 qAbs(m_fixedInc));

        if (maxMixAmount > samplesToWrite) {
            maxMixAmount = samplesToWrite;
        }

        if (maxMixAmount > 0) {
            amount = mixBlock(target+totalMixed * 2, maxMixAmount);

            if (amount == 0) {
                // Error!
                break;
            }

            totalMixed += amount;
        }
        else {
            amount = 0;
            m_fixedPos = m_fixedInc > 0 ? channelLength<<12 : 0;
        }

        // The sample ended. Check the looping variables and see what to do.
        if ((m_fixedPos >> 12) >= channelLength || m_fixedPos <= 0) {
            m_fixedPos += m_fixedInc > 0 ? -((qint64)channelLength << 12) :
                ((qint64)channelLength << 12);

            if (m_loopCount > 0)
            {
                m_loopCount--;
            }

            if (m_loopCount == 0) {
                // No more loops, stop the sample and return the amount of
                // samples already mixed.
                stop();
                break;
            }
        }

        samplesToWrite -= amount;

        if (samplesToWrite < 1)
            break;
    }

    return totalMixed * 2;
}

/*!
  Play/resume playback.
*/
void VorbisSource::play()
{
    m_finished = false;
	m_paused = false;
}

/*!
  Stop/pause playback. Calling play() after stop() continues playback from the
  position where the playback was stopped.
*/
void VorbisSource::stop()
{
    m_finished = true;
	m_paused = false;
}

/*!
  Sets \a speed as the speed of which the buffer is played in. The given
  argument value should not be zero. 1.0 indicates 100 % forward speed, -1.0
  100 % reverse speed.
*/
void VorbisSource::setSpeed(float speed)
{
    m_fixedInc = (int)(((float)m_decoder->fileInfo()->sample_rate *
        GEMaxAudioSpeedValue * speed) / (float)AUDIO_FREQUENCY);

    if (!m_fixedInc)
        m_fixedInc = 1;
}

/*!
  Sets the loop count to \a count. If the argument value is -1, the
  buffer is looped forever.
*/
void VorbisSource::setLoopCount(int count)
{
    DEBUG_INFO("Setting the loop count to " << count);
    m_loopCount = count;
}

/*!
  Sets playback position to given value. The given argument is in units of PCM
  audio samples per channel. 0 indicates beginning of the buffer and -1 the
  end of the buffer.
*/
void VorbisSource::seek(quint64 samplePos)
{
    const unsigned int channelLength = m_decoder->decodedLength();

    if (samplePos > channelLength)
        samplePos = channelLength;

    m_fixedPos = (quint64)samplePos << 12;
}

/*!
  Return the current playback position in units of PCM audio samples per
  channel.
*/
quint64 VorbisSource::position()
{
    return (m_fixedPos >> 12);
}

/*!
  Return the length of the stream in units of PCM audio samples per
  channel.
*/
quint64 VorbisSource::length()
{
    return m_decoder->decodedLength();
}

/*!
  Sets \a volume for the left channel. The given argument value should be
  between 0.0 and 1.0 since 1.0 indicates 100 %.
*/
void VorbisSource::setLeftVolume(float volume)
{
    m_fixedLeftVolume = (int)(GEMaxAudioVolumeValue * volume);
}

/*!
  Sets \a volume for the right channel. The given argument value should be
  between 0.0 and 1.0 since 1.0 indicates 100 %.
*/
void VorbisSource::setRightVolume(float volume)
{
    m_fixedRightVolume = (int)(GEMaxAudioVolumeValue * volume);
}

int VorbisSource::mixBlock(AUDIO_SAMPLE_TYPE *target, int samplesToMix)
{
    AUDIO_SAMPLE_TYPE *t_target = target + samplesToMix * 2;
    qint64 sourcepos(0);

    if (m_decoder->fileInfo()->channels == 2) {
        // Stereo
        while (target != t_target) {
            sourcepos = m_fixedPos >> 12;
            *target++ = ((((m_decoder->at(sourcepos * 2) *
                            (4096 - (m_fixedPos & 4095)) +
                            m_decoder->at((sourcepos + 1) * 2) *
                            (m_fixedPos & 4095)) >> 12) *
                          m_fixedLeftVolume) >> 12);

            *target++ = ((((m_decoder->at(sourcepos * 2 + 1) *
                            (4096 - (m_fixedPos & 4095)) +
                            m_decoder->at((sourcepos + 1) * 2 + 1) *
                            (m_fixedPos & 4095) ) >> 12) *
                          m_fixedRightVolume) >> 12);
            m_fixedPos += m_fixedInc;
        }
    }
    else {
        // Mono
        int temp(0);

        while (target != t_target) {
            sourcepos = m_fixedPos >> 12;
            temp = ((m_decoder->at(sourcepos) *
                     (4096 - (m_fixedPos & 4095)) +
                     m_decoder->at(sourcepos + 1) *
                     (m_fixedPos & 4095)) >> 12);
            *target++ = ((temp * m_fixedLeftVolume) >> 12);
            *target++ = ((temp * m_fixedRightVolume) >> 12);
            m_fixedPos += m_fixedInc;
        }
    }

    return samplesToMix;
}


code:
/**
 * Copyright (c) 2012 Nokia Corporation.
 * All rights reserved.
 *
 * Part of the Qt GameEnabler.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

#ifndef GEOggSource_H
#define GEOggSource_H

#include <QPointer>
#include "geglobal.h"
#include "audiosourceif.h"
#include "audioeffect.h"

namespace GE {

// Forward declarations
class VorbisDecoder;

class Q_GE_EXPORT VorbisSource : public AudioSource
{
    Q_OBJECT

public:
    VorbisSource(QString filename, QObject *parent = 0);
    virtual ~VorbisSource();

public:
    inline bool isFinished() const { return m_finished; }
    inline void setDestroyWhenFinished(bool set) { m_destroyWhenFinished = set; }
    inline bool destroyWhenFinished() const { return m_destroyWhenFinished; }

public: // From AudioSource
    bool canBeDestroyed();
    int pullAudio(AUDIO_SAMPLE_TYPE *target, int bufferLength);

public slots:
    void play();
    void stop();
	void pause() { m_paused = true; }
	void resume() { m_paused = false; }
    void setSpeed(float speed);
    void seek(quint64 samplePos);
    quint64 position();
    quint64 length();
    void setLeftVolume(float volume);
    void setRightVolume(float volume);
    float leftVolume() { return m_fixedLeftVolume / 4096.0f; }
    float rightVolume() { return m_fixedRightVolume / 4096.0f; }
    void setLoopCount(int count);

protected:
    int mixBlock(AUDIO_SAMPLE_TYPE *target, int bufferLength);

signals:
    void finished();

protected: // Data
    VorbisDecoder *m_decoder;
    bool m_finished;
	bool m_paused;
    bool m_destroyWhenFinished;
    qint64 m_fixedPos;
    int m_fixedInc;
    int m_fixedLeftVolume;
    int m_fixedRightVolume;
    int m_loopCount;
};

} // namespace GE

#endif // GEOggSource_H


code:
LBITS := $(shell getconf LONG_BIT)
ifeq ($(LBITS),64)
FMODEX = libfmodex64
FMODEXL = libfmodexL64
VERSION = 4.38.00
LIBDIR     = lib64/api/lib
HDRDIR     = lib64/api/inc
else
FMODEX = libfmodex
FMODEXL = libfmodexL
VERSION = 4.36.01
LIBDIR     = api/lib
HDRDIR     = api/inc
endif

DESTLIBDIR = /usr/local/lib
DESTHDRDIR = /usr/local/include/fmodex

all:
	@echo "Possible targets:"
	@echo "'make fmod_examples'  - Build all examples"
	@echo "'make install'        - Install FMOD Ex libraries and headers"
	@echo "'make uninstall'      - Uninstall FMOD Ex libraries and headers"

fmod_examples:
	cd examples/3d && make
	cd examples/cdplayer && make
	cd examples/channelgroups && make
	cd examples/dsp_effectperspeaker && make
	cd examples/dsp_custom && make
	cd examples/effects && make
	cd examples/filecallbacks && make
	cd examples/generatetone && make
	cd examples/loadfrommemory && make
	cd examples/multiplesoundcard && make
	cd examples/multispeakeroutput && make
	cd examples/netstream && make
	cd examples/offlinedecoding && make
	cd examples/pitchdetection && make
	cd examples/playlist && make
	cd examples/playsound && make
	cd examples/playstream && make
	cd examples/plugin_dev/codec_raw && make
	cd examples/plugin_dev/dsp_gain && make
	cd examples/readtags && make
	cd examples/realtimestitching && make
	cd examples/recording && make
	cd examples/recordtodisk && make
	cd examples/ripnetstream && make
	cd examples/submixing && make
	cd examples/useplugins && make
	cd examples/usercreatedsound && make
	cd fmoddesignerapi/examples/effects && make
	cd fmoddesignerapi/examples/info_only && make
	cd fmoddesignerapi/examples/load_data && make
	cd fmoddesignerapi/examples/max_playbacks && make
	cd fmoddesignerapi/examples/parameters && make
	cd fmoddesignerapi/examples/programmer_selected && make
	cd fmoddesignerapi/examples/programmer_sound && make
	cd fmoddesignerapi/examples/simple_event && make

install:
	@echo "Installing FMOD Ex libraries and headers..."
	cp -f ${LIBDIR}/${FMODEX}-${VERSION}.so ${DESTLIBDIR}
	cp -f ${LIBDIR}/${FMODEXL}-${VERSION}.so ${DESTLIBDIR}
	ln -s -f ${DESTLIBDIR}/${FMODEX}-${VERSION}.so ${DESTLIBDIR}/${FMODEX}.so
	ln -s -f ${DESTLIBDIR}/${FMODEXL}-${VERSION}.so ${DESTLIBDIR}/${FMODEXL}.so
	ldconfig -n ${DESTLIBDIR}
	mkdir -p ${DESTHDRDIR}
	cp -f ${HDRDIR}/*.h* ${DESTHDRDIR}
	@echo "done."

uninstall:
	@echo "Uninstalling FMOD Ex libraries..."
	rm -f ${DESTLIBDIR}/${FMODEX}.so
	rm -f ${DESTLIBDIR}/${FMODEXL}.so
	rm -f ${DESTLIBDIR}/${FMODEX}-${VERSION}.so
	rm -f ${DESTLIBDIR}/${FMODEXL}-${VERSION}.so
	ldconfig -n ${DESTLIBDIR}
	rm -rf ${DESTHDRDIR}
	@echo "done."