Exemplo n.º 1
0
/* Read database to memory
 */
int xs_sldb_read(xs_sldb_t *db, const char *dbFilename)
{
    FILE *inFile;
    char inLine[XS_BUF_SIZE];
    size_t lineNum;
    sldb_node_t *tmnode;
    assert(db);

    /* Try to open the file */
    if ((inFile = fopen(dbFilename, "r")) == NULL) {
        xs_error("Could not open SongLengthDB '%s'\n", dbFilename);
        return -1;
    }

    /* Read and parse the data */
    lineNum = 0;

    while (fgets(inLine, XS_BUF_SIZE, inFile) != NULL) {
        size_t linePos = 0;
        lineNum++;

        xs_findnext(inLine, &linePos);

        /* Check if it is datafield */
        if (isxdigit(inLine[linePos])) {
            /* Check the length of the hash */
            int hashLen;
            for (hashLen = 0; inLine[linePos] && isxdigit(inLine[linePos]); hashLen++, linePos++);

            if (hashLen != XS_MD5HASH_LENGTH_CH) {
                xs_error("Invalid MD5-hash in SongLengthDB file '%s' line #%d!\n",
                    dbFilename, (int)lineNum);
            } else {
                /* Parse and add node to db */
                if ((tmnode = xs_sldb_read_entry(inLine)) != NULL) {
                    xs_sldb_node_insert(db, tmnode);
                } else {
                    xs_error("Invalid entry in SongLengthDB file '%s' line #%d!\n",
                        dbFilename, (int)lineNum);
                }
            }
        } else if ((inLine[linePos] != ';') && (inLine[linePos] != '[') && (inLine[linePos] != 0)) {
            xs_error("Invalid line in SongLengthDB file '%s' line #%d\n",
                dbFilename, (int)lineNum);
        }

    }

    /* Close the file */
    fclose(inFile);

    return 0;
}
Exemplo n.º 2
0
/*
 * Initialization functions
 */
void xs_reinit(void)
{
    XSDEBUG("xs_reinit() thread = %p\n", g_thread_self());

    XS_MUTEX_LOCK(xs_status);
    XS_MUTEX_LOCK(xs_cfg);

    /* Initialize status and sanitize configuration */
    memset(&xs_status, 0, sizeof(xs_status));

    if (xs_cfg.audioFrequency < 8000)
        xs_cfg.audioFrequency = 8000;

    if (xs_cfg.oversampleFactor < XS_MIN_OVERSAMPLE)
        xs_cfg.oversampleFactor = XS_MIN_OVERSAMPLE;
    else if (xs_cfg.oversampleFactor > XS_MAX_OVERSAMPLE)
        xs_cfg.oversampleFactor = XS_MAX_OVERSAMPLE;

    if (xs_cfg.audioChannels != XS_CHN_MONO)
        xs_cfg.oversampleEnable = FALSE;

    xs_status.audioFrequency = xs_cfg.audioFrequency;
    xs_status.audioBitsPerSample = xs_cfg.audioBitsPerSample;
    xs_status.audioChannels = xs_cfg.audioChannels;
    xs_status.audioFormat = -1;
    xs_status.oversampleEnable = xs_cfg.oversampleEnable;
    xs_status.oversampleFactor = xs_cfg.oversampleFactor;

    /* Try to initialize emulator engine */
    xs_init_emu_engine(&xs_cfg.playerEngine, &xs_status);

    /* Get settings back, in case the chosen emulator backend changed them */
    xs_cfg.audioFrequency = xs_status.audioFrequency;
    xs_cfg.audioBitsPerSample = xs_status.audioBitsPerSample;
    xs_cfg.audioChannels = xs_status.audioChannels;
    xs_cfg.oversampleEnable = xs_status.oversampleEnable;

    XS_MUTEX_UNLOCK(xs_status);
    XS_MUTEX_UNLOCK(xs_cfg);

    /* Initialize song-length database */
    xs_songlen_close();
    if (xs_cfg.songlenDBEnable && (xs_songlen_init() != 0)) {
        xs_error("Error initializing song-length database!\n");
    }

    /* Initialize STIL database */
    xs_stil_close();
    if (xs_cfg.stilDBEnable && (xs_stil_init() != 0)) {
        xs_error("Error initializing STIL database!\n");
    }

}
Exemplo n.º 3
0
/*
 * Initialization functions
 */
bool_t xs_init(void)
{
    bool_t success;

    /* Initialize and get configuration */
    xs_init_configuration();

    pthread_mutex_lock(&xs_status_mutex);
    pthread_mutex_lock(&xs_cfg_mutex);

    /* Initialize status and sanitize configuration */
    memset(&xs_status, 0, sizeof(xs_status));

    if (xs_cfg.audioFrequency < 8000)
        xs_cfg.audioFrequency = 8000;

    xs_status.audioFrequency = xs_cfg.audioFrequency;
    xs_status.audioChannels = xs_cfg.audioChannels;

    /* Try to initialize emulator engine */
    success = xs_sidplayfp_init(&xs_status);

    /* Get settings back, in case the chosen emulator backend changed them */
    xs_cfg.audioFrequency = xs_status.audioFrequency;
    xs_cfg.audioChannels = xs_status.audioChannels;

    pthread_mutex_unlock(&xs_status_mutex);
    pthread_mutex_unlock(&xs_cfg_mutex);

    if (! success)
        return FALSE;

    /* Initialize song-length database */
    xs_songlen_close();
    if (xs_cfg.songlenDBEnable && (xs_songlen_init() != 0)) {
        xs_error("Error initializing song-length database!\n");
    }

    /* Initialize STIL database */
    xs_stil_close();
    if (xs_cfg.stilDBEnable && (xs_stil_init() != 0)) {
        xs_error("Error initializing STIL database!\n");
    }

    return TRUE;
}
Exemplo n.º 4
0
static void XS_STILDB_ERR(gint linenum, gchar *line, const gchar *fmt, ...)
{
    va_list ap;
    
    va_start(ap, fmt);
    xs_verror(fmt, ap);
    va_end(ap);
    
    xs_error("#%d: '%s'\n", linenum, line);
}
Exemplo n.º 5
0
/* Database handling functions
 */
static gboolean xs_stildb_node_realloc(stil_node_t *node, gint nsubTunes)
{
    if (node == NULL) return FALSE;

    /* Re-allocate subTune structure if needed */
    if (nsubTunes > node->nsubTunes) {
        gint clearIndex, clearLength;
        
        node->subTunes =
            (stil_subnode_t **) g_realloc(node->subTunes,
            (nsubTunes + 1) * sizeof(stil_subnode_t **));

        if (!node->subTunes) {
            xs_error("SubTune pointer structure realloc failed.\n");
            return FALSE;
        }
        
        /* Clear the newly allocated memory */
        if (node->nsubTunes == 0) {
            clearIndex = 0;
            clearLength = nsubTunes + 1;
        } else {
            clearIndex = node->nsubTunes + 1;
            clearLength = (nsubTunes - clearIndex + 1);
        }
        memset(&(node->subTunes[clearIndex]), 0, clearLength * sizeof(stil_subnode_t **));
        
        node->nsubTunes = nsubTunes;
    }

    /* Allocate memory for subTune */
    if (!node->subTunes[nsubTunes]) {
        node->subTunes[nsubTunes] = g_new0(stil_subnode_t, 1);
        
        if (node->subTunes[nsubTunes] == NULL) {
            xs_error("SubTune structure malloc failed!\n");
            return FALSE;
        }
    }

    return TRUE;
}
Exemplo n.º 6
0
gint xs_stildb_read(xs_stildb_t *db, gchar *filename)
{
    FILE *f;
    gchar line[XS_BUF_SIZE + 16];    /* Since we add some chars here and there */
    stil_node_t *node;
    gboolean error, multi;
    gint lineNum, subEntry;
    gchar *tmpLine = line;
    assert(db != NULL);

    /* Try to open the file */
    if ((f = fopen(filename, "ra")) == NULL) {
        xs_error("Could not open STILDB '%s'\n", filename);
        return -1;
    }

    /* Read and parse the data */
    lineNum = 0;
    error = FALSE;
    multi = FALSE;
    node = NULL;
    subEntry = 0;

    while (!error && fgets(line, XS_BUF_SIZE, f) != NULL) {
        gsize linePos = 0, eolPos = 0;
        line[XS_BUF_SIZE] = 0;
        xs_findeol(line, &eolPos);
        line[eolPos] = 0;
        lineNum++;
        
        tmpLine = g_convert(line, -1, "UTF-8", XS_STIL_CHARSET, NULL, NULL, NULL);

        switch (tmpLine[0]) {
        case '/':
            /* Check if we are already parsing entry */
            multi = FALSE;
            if (node != NULL) {
                XS_STILDB_ERR(lineNum, tmpLine,
                    "New entry found before end of current ('%s')!\n",
                    node->filename);
                xs_stildb_node_free(node);
                node = NULL;
            }

            /* A new node */
            subEntry = 0;
            if ((node = xs_stildb_node_new(tmpLine)) == NULL) {
                XS_STILDB_ERR(lineNum, tmpLine,
                    "Could not allocate new STILdb-node!\n");
                error = TRUE;
            }
            break;

        case '(':
            /* A new sub-entry */
            multi = FALSE;
            linePos++;
            if (tmpLine[linePos] == '#') {
                linePos++;
                if (isdigit(tmpLine[linePos])) {
                    gsize savePos = linePos;
                    xs_findnum(tmpLine, &linePos);
                    tmpLine[linePos] = 0;
                    subEntry = atol(&tmpLine[savePos]);

                    /* Sanity check */
                    if (subEntry < 1) {
                        gchar *tmp = node != NULL ? node->filename : NULL;
                        XS_STILDB_ERR(lineNum, tmpLine,
                            "Number of subEntry (%d) for '%s' is invalid\n",
                            subEntry, tmp);
                        subEntry = 0;
                    }
                } else {
                    XS_STILDB_ERR(lineNum, tmpLine,
                        "Syntax error, expected subEntry number.\n");
                    subEntry = 0;
                }
            } else {
                XS_STILDB_ERR(lineNum, tmpLine,
                    "Syntax error, expected '#' before subEntry number.\n");
                subEntry = 0;
            }

            break;

        case 0:
        case '#':
        case '\n':
        case '\r':
            /* End of entry/field */
            multi = FALSE;
            if (node != NULL) {
                xs_stildb_node_insert(db, node);
                node = NULL;
            }
            break;

        default:
            /* Check if we are parsing an entry */
            xs_findnext(tmpLine, &linePos);
            
            if (node == NULL) {
                XS_STILDB_ERR(lineNum, tmpLine,
                    "Entry data encountered outside of entry or syntax error!\n");
                break;
            }

            if (!xs_stildb_node_realloc(node, subEntry)) {
                XS_STILDB_ERR(lineNum, tmpLine,
                    "Could not (re)allocate memory for subEntries!\n");
                error = TRUE;
                break;
            }
            
            /* Some other type */
            if (strncmp(tmpLine, "   NAME:", 8) == 0) {
                XS_STILDB_MULTI;
                g_free(node->subTunes[subEntry]->name);
                node->subTunes[subEntry]->name = g_strdup(&tmpLine[9]);
            } else if (strncmp(tmpLine, "  TITLE:", 8) == 0) {
                XS_STILDB_MULTI;
                multi = TRUE;
                if (!node->subTunes[subEntry]->title)
                    node->subTunes[subEntry]->title = g_strdup(&tmpLine[9]);
                xs_pstrcat(&(node->subTunes[subEntry]->info), &tmpLine[2]);
            } else if (strncmp(tmpLine, " AUTHOR:", 8) == 0) {
                XS_STILDB_MULTI;
                g_free(node->subTunes[subEntry]->author);
                node->subTunes[subEntry]->author = g_strdup(&tmpLine[9]);
            } else if (strncmp(tmpLine, " ARTIST:", 8) == 0) {
                XS_STILDB_MULTI;
                multi = TRUE;
                xs_pstrcat(&(node->subTunes[subEntry]->info), &tmpLine[1]);
            } else if (strncmp(tmpLine, "COMMENT:", 8) == 0) {
                XS_STILDB_MULTI;
                multi = TRUE;
                xs_pstrcat(&(node->subTunes[subEntry]->info), tmpLine);
            } else {
                if (multi) {
                    xs_pstrcat(&(node->subTunes[subEntry]->info), " ");
                    xs_pstrcat(&(node->subTunes[subEntry]->info), &tmpLine[linePos]);
                } else {
                    XS_STILDB_ERR(lineNum, tmpLine,
                    "Entry continuation found when multi == FALSE.\n");
                }
            }
            break;
        }
        g_free(tmpLine);
    } /* while */

    /* Check if there is one remaining node */
    if (node != NULL)
        xs_stildb_node_insert(db, node);

    /* Close the file */
    fclose(f);

    return 0;
}
Exemplo n.º 7
0
/*
 * Start playing the given file
 */
bool_t xs_play_file(const char *filename, VFSFile *file)
{
    xs_tuneinfo_t *tmpTune;
    int audioBufSize, bufRemaining, tmpLength, subTune = -1;
    char *audioBuffer = NULL, *oversampleBuffer = NULL;
    Tuple *tmpTuple;

    uri_parse (filename, NULL, NULL, NULL, & subTune);

    /* Get tune information */
    pthread_mutex_lock(&xs_status_mutex);

    if (! (xs_status.tuneInfo = xs_sidplayfp_getinfo (filename)))
    {
        pthread_mutex_unlock(&xs_status_mutex);
        return FALSE;
    }

    /* Initialize the tune */
    if (! xs_sidplayfp_load (& xs_status, filename))
    {
        pthread_mutex_unlock(&xs_status_mutex);
        xs_tuneinfo_free(xs_status.tuneInfo);
        xs_status.tuneInfo = NULL;
        return FALSE;
    }

    bool_t error = FALSE;

    /* Set general status information */
    tmpTune = xs_status.tuneInfo;

    if (subTune < 1 || subTune > xs_status.tuneInfo->nsubTunes)
        xs_status.currSong = xs_status.tuneInfo->startTune;
    else
        xs_status.currSong = subTune;

    int channels = xs_status.audioChannels;

    /* Allocate audio buffer */
    audioBufSize = xs_status.audioFrequency * channels * FMT_SIZEOF (FMT_S16_NE);
    if (audioBufSize < 512) audioBufSize = 512;

    audioBuffer = (char *) malloc(audioBufSize);
    if (audioBuffer == NULL) {
        xs_error("Couldn't allocate memory for audio data buffer!\n");
        pthread_mutex_unlock(&xs_status_mutex);
        goto xs_err_exit;
    }

    /* Check minimum playtime */
    tmpLength = tmpTune->subTunes[xs_status.currSong - 1].tuneLength;
    if (xs_cfg.playMinTimeEnable && (tmpLength >= 0)) {
        if (tmpLength < xs_cfg.playMinTime)
            tmpLength = xs_cfg.playMinTime;
    }

    /* Initialize song */
    if (!xs_sidplayfp_initsong(&xs_status)) {
        xs_error("Couldn't initialize SID-tune '%s' (sub-tune #%i)!\n",
            tmpTune->sidFilename, xs_status.currSong);
        pthread_mutex_unlock(&xs_status_mutex);
        goto xs_err_exit;
    }

    /* Open the audio output */
    if (!aud_input_open_audio(FMT_S16_NE, xs_status.audioFrequency, channels))
    {
        xs_error("Couldn't open audio output (fmt=%x, freq=%i, nchan=%i)!\n",
            FMT_S16_NE,
            xs_status.audioFrequency,
            channels);

        pthread_mutex_unlock(&xs_status_mutex);
        goto xs_err_exit;
    }

    /* Set song information for current subtune */
    xs_sidplayfp_updateinfo(&xs_status);
    tmpTuple = tuple_new_from_filename(tmpTune->sidFilename);
    xs_get_song_tuple_info(tmpTuple, tmpTune, xs_status.currSong);

    pthread_mutex_unlock(&xs_status_mutex);

    aud_input_set_tuple(tmpTuple);

    while (! aud_input_check_stop ())
    {
        bufRemaining = xs_sidplayfp_fillbuffer(&xs_status, audioBuffer, audioBufSize);

        aud_input_write_audio (audioBuffer, bufRemaining);

        /* Check if we have played enough */
        if (xs_cfg.playMaxTimeEnable) {
            if (xs_cfg.playMaxTimeUnknown) {
                if (tmpLength < 0 &&
                    aud_input_written_time() >= xs_cfg.playMaxTime * 1000)
                    break;
            } else {
                if (aud_input_written_time() >= xs_cfg.playMaxTime * 1000)
                    break;
            }
        }

        if (tmpLength >= 0) {
            if (aud_input_written_time() >= tmpLength * 1000)
                break;
        }
    }

DONE:
    free(audioBuffer);
    free(oversampleBuffer);

    /* Set playing status to false (stopped), thus when
     * XMMS next calls xs_get_time(), it can return appropriate
     * value "not playing" status and XMMS knows to move to
     * next entry in the playlist .. or whatever it wishes.
     */
    pthread_mutex_lock(&xs_status_mutex);

    /* Free tune information */
    xs_sidplayfp_delete(&xs_status);
    xs_tuneinfo_free(xs_status.tuneInfo);
    xs_status.tuneInfo = NULL;
    pthread_mutex_unlock(&xs_status_mutex);

    /* Exit the playing thread */
    return ! error;

xs_err_exit:
    error = TRUE;
    goto DONE;
}
Exemplo n.º 8
0
static int xs_get_sid_hash(const char *filename, xs_md5hash_t hash)
{
    VFSFile *inFile;
    xs_md5state_t inState;
    psidv1_header_t psidH;
    psidv2_header_t psidH2;
    uint8_t *songData;
    uint8_t ib8[2], i8;
    int index, result;

    /* Try to open the file */
    if ((inFile = vfs_fopen(filename, "rb")) == NULL)
        return -1;

    /* Read PSID header in */
    if (vfs_fread(psidH.magicID, 1, sizeof psidH.magicID, inFile) < sizeof psidH.magicID) {
        vfs_fclose(inFile);
        return -1;
    }

    if (strncmp(psidH.magicID, "PSID", 4) && strncmp(psidH.magicID, "RSID", 4)) {
        vfs_fclose(inFile);
        xs_error("Not a PSID or RSID file '%s'\n", filename);
        return -2;
    }

    psidH.version = xs_fread_be16(inFile);
    psidH.dataOffset = xs_fread_be16(inFile);
    psidH.loadAddress = xs_fread_be16(inFile);
    psidH.initAddress = xs_fread_be16(inFile);
    psidH.playAddress = xs_fread_be16(inFile);
    psidH.nSongs = xs_fread_be16(inFile);
    psidH.startSong = xs_fread_be16(inFile);
    psidH.speed = xs_fread_be32(inFile);

    if (vfs_fread(psidH.sidName, 1, sizeof psidH.sidName, inFile) < sizeof psidH.sidName
     || vfs_fread(psidH.sidAuthor, 1, sizeof psidH.sidAuthor, inFile) < sizeof psidH.sidAuthor
     || vfs_fread(psidH.sidCopyright, 1, sizeof psidH.sidCopyright, inFile) < sizeof psidH.sidCopyright) {
        vfs_fclose(inFile);
        xs_error("Error reading SID file header from '%s'\n", filename);
        return -4;
    }

    /* Check if we need to load PSIDv2NG header ... */
    psidH2.flags = 0;    /* Just silence a stupid gcc warning */

    if (psidH.version == 2) {
        /* Yes, we need to */
        psidH2.flags = xs_fread_be16(inFile);
        psidH2.startPage = vfs_getc(inFile);
        psidH2.pageLength = vfs_getc(inFile);
        psidH2.reserved = xs_fread_be16(inFile);
    }

    /* Allocate buffer */
    songData = (uint8_t *) malloc(XS_SIDBUF_SIZE * sizeof(uint8_t));
    if (!songData) {
        vfs_fclose(inFile);
        xs_error("Error allocating temp data buffer for file '%s'\n", filename);
        return -3;
    }

    /* Read data to buffer */
    result = vfs_fread(songData, sizeof(uint8_t), XS_SIDBUF_SIZE, inFile);
    vfs_fclose(inFile);

    /* Initialize and start MD5-hash calculation */
    xs_md5_init(&inState);

    if (psidH.loadAddress == 0) {
        /* Strip load address (2 first bytes) */
        xs_md5_append(&inState, &songData[2], result - 2);
    } else {
        /* Append "as is" */
        xs_md5_append(&inState, songData, result);
    }

    /* Free buffer */
    free(songData);

    /* Append header data to hash */
#define XSADDHASH(QDATAB) do {                    \
    ib8[0] = (QDATAB & 0xff);                \
    ib8[1] = (QDATAB >> 8);                    \
    xs_md5_append(&inState, (uint8_t *) &ib8, sizeof(ib8));    \
    } while (0)

    XSADDHASH(psidH.initAddress);
    XSADDHASH(psidH.playAddress);
    XSADDHASH(psidH.nSongs);
#undef XSADDHASH

    /* Append song speed data to hash */
    i8 = 0;
    for (index = 0; (index < psidH.nSongs) && (index < 32); index++) {
        i8 = (psidH.speed & (1 << index)) ? 60 : 0;
        xs_md5_append(&inState, &i8, sizeof(i8));
    }

    /* Rest of songs (more than 32) */
    for (index = 32; index < psidH.nSongs; index++) {
        xs_md5_append(&inState, &i8, sizeof(i8));
    }

    /* PSIDv2NG specific */
    if (psidH.version == 2) {
        /* SEE SIDPLAY HEADERS FOR INFO */
        i8 = (psidH2.flags >> 2) & 3;
        if (i8 == 2)
            xs_md5_append(&inState, &i8, sizeof(i8));
    }
Exemplo n.º 9
0
/* Parse one SLDB definition line, return SLDB node
 */
sldb_node_t * xs_sldb_read_entry(char *inLine)
{
    size_t linePos;
    int i;
    bool_t isOK;
    sldb_node_t *tmnode;

    /* Allocate new node */
    tmnode = (sldb_node_t *) malloc(sizeof(sldb_node_t));
    if (!tmnode) {
        xs_error("Error allocating new node. Fatal error.\n");
        return NULL;
    }
    memset(tmnode, 0, sizeof(sldb_node_t));

    /* Get hash value */
    linePos = 0;
    for (i = 0; i < XS_MD5HASH_LENGTH; i++, linePos += 2) {
        unsigned tmpu;
        sscanf(&inLine[linePos], "%2x", &tmpu);
        tmnode->md5Hash[i] = tmpu;
    }

    /* Get playtimes */
    if (inLine[linePos] != 0) {
        if (inLine[linePos] != '=') {
            xs_error("'=' expected on column #%d.\n", (int)linePos);
            xs_sldb_node_free(tmnode);
            return NULL;
        } else {
            size_t tmpLen, savePos;

            /* First playtime is after '=' */
            savePos = ++linePos;
            tmpLen = strlen(inLine);

            /* Get number of sub-tune lengths */
            isOK = TRUE;
            while ((linePos < tmpLen) && isOK) {
                xs_findnext(inLine, &linePos);

                if (xs_sldb_gettime(inLine, &linePos) >= 0)
                    tmnode->nlengths++;
                else
                    isOK = FALSE;
            }

            /* Allocate memory for lengths */
            if (tmnode->nlengths > 0) {
                tmnode->lengths = (int *) malloc(tmnode->nlengths * sizeof(int));
                if (!tmnode->lengths) {
                    xs_error("Could not allocate memory for node.\n");
                    xs_sldb_node_free(tmnode);
                    return NULL;
                }
                memset(tmnode->lengths, 0, tmnode->nlengths * sizeof(int));
            } else {
                xs_sldb_node_free(tmnode);
                return NULL;
            }

            /* Read lengths in */
            i = 0;
            linePos = savePos;
            isOK = TRUE;
            while ((linePos < tmpLen) && (i < tmnode->nlengths) && isOK) {
                int l;

                xs_findnext(inLine, &linePos);

                l = xs_sldb_gettime(inLine, &linePos);
                if (l >= 0)
                    tmnode->lengths[i] = l;
                else
                    isOK = FALSE;

                i++;
            }

            if (!isOK) {
                xs_sldb_node_free(tmnode);
                return NULL;
            } else
                return tmnode;
        }
    }

    xs_sldb_node_free(tmnode);
    return NULL;
}
Exemplo n.º 10
0
/*
 * Start playing the given file
 */
gboolean xs_play_file(InputPlayback *pb, const gchar *filename, VFSFile *file, gint start_time, gint stop_time, gboolean pause)
{
    xs_tuneinfo_t *tmpTune;
    gint audioBufSize, bufRemaining, tmpLength, subTune = -1;
    gchar *audioBuffer = NULL, *oversampleBuffer = NULL;
    Tuple *tmpTuple;

    assert(pb);
    assert(xs_status.sidPlayer != NULL);

    uri_parse (filename, NULL, NULL, NULL, & subTune);

    /* Get tune information */
    XS_MUTEX_LOCK(xs_status);

    if (! (xs_status.tuneInfo = xs_status.sidPlayer->plrGetSIDInfo (filename)))
    {
        XS_MUTEX_UNLOCK(xs_status);
        return FALSE;
    }

    /* Initialize the tune */
    if (! xs_status.sidPlayer->plrLoadSID (& xs_status, filename))
    {
        XS_MUTEX_UNLOCK(xs_status);
        xs_tuneinfo_free(xs_status.tuneInfo);
        xs_status.tuneInfo = NULL;
        return FALSE;
    }

    gboolean error = FALSE;

    /* Set general status information */
    tmpTune = xs_status.tuneInfo;

    if (subTune < 1 || subTune > xs_status.tuneInfo->nsubTunes)
        xs_status.currSong = xs_status.tuneInfo->startTune;
    else
        xs_status.currSong = subTune;

    XSDEBUG("subtune #%i selected (#%d wanted), initializing...\n", xs_status.currSong, subTune);

    gint channels = (xs_status.audioChannels == XS_CHN_AUTOPAN) ? 2 :
     xs_status.audioChannels;

    /* Allocate audio buffer */
    audioBufSize = xs_status.audioFrequency * channels *
     xs_status.audioBitsPerSample / (8 * 4);
    if (audioBufSize < 512) audioBufSize = 512;

    audioBuffer = (gchar *) g_malloc(audioBufSize);
    if (audioBuffer == NULL) {
        xs_error("Couldn't allocate memory for audio data buffer!\n");
        XS_MUTEX_UNLOCK(xs_status);
        goto xs_err_exit;
    }

    if (xs_status.oversampleEnable) {
        oversampleBuffer = (gchar *) g_malloc(audioBufSize * xs_status.oversampleFactor);
        if (oversampleBuffer == NULL) {
            xs_error("Couldn't allocate memory for audio oversampling buffer!\n");
            XS_MUTEX_UNLOCK(xs_status);
            goto xs_err_exit;
        }
    }


    /* Check minimum playtime */
    tmpLength = tmpTune->subTunes[xs_status.currSong - 1].tuneLength;
    if (xs_cfg.playMinTimeEnable && (tmpLength >= 0)) {
        if (tmpLength < xs_cfg.playMinTime)
            tmpLength = xs_cfg.playMinTime;
    }

    /* Initialize song */
    if (!xs_status.sidPlayer->plrInitSong(&xs_status)) {
        xs_error("Couldn't initialize SID-tune '%s' (sub-tune #%i)!\n",
            tmpTune->sidFilename, xs_status.currSong);
        XS_MUTEX_UNLOCK(xs_status);
        goto xs_err_exit;
    }

    /* Open the audio output */
    XSDEBUG("open audio output (%d, %d, %d)\n",
        xs_status.audioFormat, xs_status.audioFrequency, channels);

    if (!pb->output->open_audio(xs_status.audioFormat, xs_status.audioFrequency,
     channels))
    {
        xs_error("Couldn't open audio output (fmt=%x, freq=%i, nchan=%i)!\n",
            xs_status.audioFormat,
            xs_status.audioFrequency,
            channels);

        XS_MUTEX_UNLOCK(xs_status);
        goto xs_err_exit;
    }

    /* Set song information for current subtune */
    XSDEBUG("foobar #1\n");
    xs_status.sidPlayer->plrUpdateSIDInfo(&xs_status);
    tmpTuple = tuple_new_from_filename(tmpTune->sidFilename);
    xs_get_song_tuple_info(tmpTuple, tmpTune, xs_status.currSong);

    xs_status.stop_flag = FALSE;
    XS_MUTEX_UNLOCK(xs_status);

    pb->set_tuple(pb, tmpTuple);
    pb->set_params (pb, -1, xs_status.audioFrequency, channels);
    pb->set_pb_ready(pb);

    XSDEBUG("playing\n");

    while (1)
    {
        XS_MUTEX_LOCK (xs_status);

        if (xs_status.stop_flag)
        {
            XS_MUTEX_UNLOCK (xs_status);
            break;
        }

        XS_MUTEX_UNLOCK (xs_status);

        /* Render audio data */
        if (xs_status.oversampleEnable) {
            /* Perform oversampled rendering */
            bufRemaining = xs_status.sidPlayer->plrFillBuffer(
                &xs_status,
                oversampleBuffer,
                (audioBufSize * xs_status.oversampleFactor));

            bufRemaining /= xs_status.oversampleFactor;

            /* Execute rate-conversion with filtering */
            if (xs_filter_rateconv(audioBuffer, oversampleBuffer,
                xs_status.audioFormat, xs_status.oversampleFactor, bufRemaining) < 0) {
                xs_error("Oversampling rate-conversion pass failed.\n");
                goto xs_err_exit;
            }
        } else {
            bufRemaining = xs_status.sidPlayer->plrFillBuffer(
                &xs_status, audioBuffer, audioBufSize);
        }

        pb->output->write_audio (audioBuffer, bufRemaining);

        /* Check if we have played enough */
        if (xs_cfg.playMaxTimeEnable) {
            if (xs_cfg.playMaxTimeUnknown) {
                if (tmpLength < 0 &&
                    pb->output->written_time() >= xs_cfg.playMaxTime * 1000)
                    break;
            } else {
                if (pb->output->written_time() >= xs_cfg.playMaxTime * 1000)
                    break;
            }
        }

        if (tmpLength >= 0) {
            if (pb->output->written_time() >= tmpLength * 1000)
                break;
        }
    }

DONE:
    XSDEBUG("out of playing loop\n");

    g_free(audioBuffer);
    g_free(oversampleBuffer);

    /* Set playing status to false (stopped), thus when
     * XMMS next calls xs_get_time(), it can return appropriate
     * value "not playing" status and XMMS knows to move to
     * next entry in the playlist .. or whatever it wishes.
     */
    XS_MUTEX_LOCK(xs_status);
    xs_status.stop_flag = TRUE;

    /* Free tune information */
    xs_status.sidPlayer->plrDeleteSID(&xs_status);
    xs_tuneinfo_free(xs_status.tuneInfo);
    xs_status.tuneInfo = NULL;
    XS_MUTEX_UNLOCK(xs_status);

    /* Exit the playing thread */
    XSDEBUG("exiting thread, bye.\n");

    return ! error;

xs_err_exit:
    error = TRUE;
    goto DONE;
}