Ejemplo n.º 1
0
bool CDROM_Interface_Ioctl::mci_CDPlay(int start, int length) {
	DWORD flags = MCI_FROM | MCI_TO | MCI_NOTIFY;
	MCI_PLAY_PARMS mci_play;
	mci_play.dwCallback = 0;

	int m, s, f;
	FRAMES_TO_MSF(start, &m, &s, &f);
	mci_play.dwFrom = MCI_MAKE_MSF(m, s, f);

	FRAMES_TO_MSF(start+length, &m, &s, &f);
	mci_play.dwTo = MCI_MAKE_MSF(m, s, f);

	return mci_CDioctl(MCI_PLAY, flags, &mci_play);
}
Ejemplo n.º 2
0
/* Start play */
static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length)
{
	MCI_PLAY_PARMS mci_play;
	int m, s, f;
	DWORD flags;

	flags = MCI_FROM | MCI_TO | MCI_NOTIFY;
	mci_play.dwCallback = 0;
	FRAMES_TO_MSF(start, &m, &s, &f);
	mci_play.dwFrom = MCI_MAKE_MSF(m, s, f);
	FRAMES_TO_MSF(start+length, &m, &s, &f);
	mci_play.dwTo = MCI_MAKE_MSF(m, s, f);
	SDL_CD_end_position = mci_play.dwTo;
	return(SDL_SYS_CDioctl(cdrom->id, MCI_PLAY, flags, &mci_play));
}
Ejemplo n.º 3
0
/**************************************************************************
 * 			MCICDA_CalcTime				[internal]
 */
static DWORD MCICDA_CalcTime(WINE_MCICDAUDIO* wmcda, DWORD tf, DWORD dwFrame, LPDWORD lpRet)
{
    DWORD	dwTime = 0;
    UINT	wTrack;
    UINT	wMinutes;
    UINT	wSeconds;
    UINT	wFrames;
    CDROM_TOC   toc;
    DWORD       br;

    TRACE("(%p, %08X, %u);\n", wmcda, tf, dwFrame);

    switch (tf) {
    case MCI_FORMAT_MILLISECONDS:
	dwTime = (dwFrame * 1000) / CDFRAMES_PERSEC + 1;
	TRACE("MILLISECONDS %u\n", dwTime);
	*lpRet = 0;
	break;
    case MCI_FORMAT_MSF:
	wMinutes = dwFrame / CDFRAMES_PERMIN;
	wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
	wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
	dwTime = MCI_MAKE_MSF(wMinutes, wSeconds, wFrames);
	TRACE("MSF %02u:%02u:%02u -> dwTime=%u\n",
	      wMinutes, wSeconds, wFrames, dwTime);
	*lpRet = MCI_COLONIZED3_RETURN;
	break;
    case MCI_FORMAT_TMSF:
    default:	/* unknown format ! force TMSF ! ... */
        if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
                             &toc, sizeof(toc), &br, NULL))
            return 0;
	if (dwFrame < FRAME_OF_TOC(toc, toc.FirstTrack) ||
            dwFrame > FRAME_OF_TOC(toc, toc.LastTrack + 1)) {
            ERR("Out of range value %u [%u,%u]\n",
		dwFrame, FRAME_OF_TOC(toc, toc.FirstTrack),
                FRAME_OF_TOC(toc, toc.LastTrack + 1));
	    *lpRet = 0;
	    return 0;
	}
	for (wTrack = toc.FirstTrack; wTrack <= toc.LastTrack; wTrack++) {
	    if (FRAME_OF_TOC(toc, wTrack) > dwFrame)
		break;
	}
        wTrack--;
	dwFrame -= FRAME_OF_TOC(toc, wTrack);
	wMinutes = dwFrame / CDFRAMES_PERMIN;
	wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
	wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
	dwTime = MCI_MAKE_TMSF(wTrack, wMinutes, wSeconds, wFrames);
	TRACE("%02u-%02u:%02u:%02u\n", wTrack, wMinutes, wSeconds, wFrames);
	*lpRet = MCI_COLONIZED4_RETURN;
	break;
    }
    return dwTime;
}
Ejemplo n.º 4
0
static DWORD MSF_Add(DWORD d1, DWORD d2)
{
    WORD c, m, s, f;
    f = MCI_MSF_FRAME(d1)  + MCI_MSF_FRAME(d2);
    c = f / CDFRAMES_PERSEC;
    f = f % CDFRAMES_PERSEC;
    s = MCI_MSF_SECOND(d1) + MCI_MSF_SECOND(d2) + c;
    c = s / 60;
    s = s % 60;
    m = MCI_MSF_MINUTE(d1) + MCI_MSF_MINUTE(d2) + c; /* may be > 60 */
    return MCI_MAKE_MSF(m,s,f);
}
Ejemplo n.º 5
0
static void test_play(HWND hwnd)
{
    MCIDEVICEID wDeviceID;
    MCI_PARMS_UNION parm;
    MCIERROR err, ok_hw;
    DWORD numtracks, track, duration;
    DWORD factor = winetest_interactive ? 3 : 1;
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    parm.gen.dwCallback = (DWORD_PTR)hwnd; /* once to rule them all */

    err = mciSendString("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd);
    ok(!err || err == MCIERR_CANNOT_LOAD_DRIVER || err == MCIERR_MUST_USE_SHAREABLE,
       "mci open cdaudio notify returned %s\n", dbg_mcierr(err));
    test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
    /* Native returns MUST_USE_SHAREABLE when there's trouble with the hardware
     * (e.g. unreadable disk) or when Media Player already has the device open,
     * yet adding that flag does not help get past this error. */

    if(err) {
        skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err));
        return;
    }
    wDeviceID = atoi(buf);
    ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf);

    err = mciSendString("capability c has video notify", buf, sizeof(buf), hwnd);
    ok(!err, "capability video: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf);
    test_notification(hwnd, "capability notify", MCI_NOTIFY_SUCCESSFUL);

    err = mciSendString("capability c can play", buf, sizeof(buf), hwnd);
    ok(!err, "capability video: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "true"), "capability play is %s\n", buf);

    err = mciSendString("capability c", buf, sizeof(buf), NULL);
    ok(err == MCIERR_MISSING_PARAMETER, "capability nokeyword: %s\n", dbg_mcierr(err));

    parm.caps.dwItem = 0x4001;
    parm.caps.dwReturn = 0xFEEDABAD;
    err = mciSendCommand(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "GETDEVCAPS %x: %s\n", parm.caps.dwItem, dbg_mcierr(err));

    parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
    err = mciSendCommand(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
    ok(!err, "GETDEVCAPS device type: %s\n", dbg_mcierr(err));
    if(!err) ok( parm.caps.dwReturn == MCI_DEVTYPE_CD_AUDIO, "getdevcaps device type: %u\n", parm.caps.dwReturn);

    err = mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD_PTR)&parm);
    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_RECORD: %s\n", dbg_mcierr(err));

    /* Wine's MCI_MapMsgAtoW crashes on MCI_SAVE without parm->lpfilename */
    parm.save.lpfilename = "foo";
    err = mciSendCommand(wDeviceID, MCI_SAVE, 0, (DWORD_PTR)&parm);
    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_SAVE: %s\n", dbg_mcierr(err));

    /* commands from the core set are UNSUPPORTED, others UNRECOGNIZED */
    err = mciSendCommand(wDeviceID, MCI_STEP, 0, (DWORD_PTR)&parm);
    ok(err == MCIERR_UNRECOGNIZED_COMMAND, "MCI_STEP: %s\n", dbg_mcierr(err));

    parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
    parm.status.dwReturn = 0xFEEDABAD;
    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
    ok(!err, "STATUS time format: %s\n", dbg_mcierr(err));
    if(!err) ok(parm.status.dwReturn == MCI_FORMAT_MSF, "status time default format: %ld\n", parm.status.dwReturn);

    /* "CD-Audio" */
    err = mciSendString("info c product wait notify", buf, sizeof(buf), hwnd);
    ok(!err, "info product: %s\n", dbg_mcierr(err));
    test_notification(hwnd, "info notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);

    parm.status.dwItem = MCI_STATUS_MEDIA_PRESENT;
    parm.status.dwReturn = 0xFEEDABAD;
    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
    ok(err || parm.status.dwReturn == TRUE || parm.status.dwReturn == FALSE,
       "STATUS media present: %s\n", dbg_mcierr(err));

    if (parm.status.dwReturn != TRUE) {
        skip("No CD-ROM in drive.\n");
        return;
    }

    parm.status.dwItem = MCI_STATUS_MODE;
    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
    ok(!err, "STATUS mode: %s\n", dbg_mcierr(err));
    switch(parm.status.dwReturn) {
    case MCI_MODE_NOT_READY:
        skip("CD-ROM mode not ready (DVD in drive?)\n");
        return;
    case MCI_MODE_OPEN: /* should not happen with MEDIA_PRESENT */
        skip("CD-ROM drive is open\n");
        /* set door closed may not work. */
        return;
    default: /* play/record/seek/pause */
        ok(parm.status.dwReturn==MCI_MODE_STOP, "STATUS mode is %lx\n", parm.status.dwReturn);
        /* fall through */
    case MCI_MODE_STOP: /* normal */
        break;
    }

    /* Initial mode is "stopped" with a CD in drive */
    err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
    ok(!err, "status mode: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "stopped"), "status mode is initially %s\n", buf);

    err = mciSendString("status c ready", buf, sizeof(buf), hwnd);
    ok(!err, "status ready: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "true"), "status ready with media is %s\n", buf);

    err = mciSendString("info c product identity", buf, sizeof(buf), hwnd);
    ok(!err, "info 2flags: %s\n", dbg_mcierr(err)); /* not MCIERR_FLAGS_NOT_COMPATIBLE */
    /* Precedence rule p>u>i verified experimentally, not tested here. */

    err = mciSendString("info c identity", buf, sizeof(buf), hwnd);
    ok(!err || err == MCIERR_HARDWARE, "info identity: %s\n", dbg_mcierr(err));
    /* a blank disk causes MCIERR_HARDWARE and other commands to fail likewise. */
    ok_hw = err;

    err = mciSendString("info c upc", buf, sizeof(buf), hwnd);
    ok(err == ok_hw || err == MCIERR_NO_IDENTITY, "info upc: %s\n", dbg_mcierr(err));

    parm.status.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
    parm.status.dwReturn = 0;
    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
    ok(err == ok_hw, "STATUS number of tracks: %s\n", dbg_mcierr(err));
    numtracks = parm.status.dwReturn;
    /* cf. MAXIMUM_NUMBER_TRACKS */
    ok(0 < numtracks && numtracks <= 99, "number of tracks=%ld\n", parm.status.dwReturn);

    err = mciSendString("status c length", buf, sizeof(buf), hwnd);
    ok(err == ok_hw, "status length: %s\n", dbg_mcierr(err));
    if(!err) trace("CD length %s\n", buf);

    if(err) { /* MCIERR_HARDWARE when given a blank disk */
        skip("status length %s (blank disk?)\n", dbg_mcierr(ok_hw));
        return;
    }

    /* Linux leaves the drive at some random position,
     * native initialises to the start position below. */
    err = mciSendString("status c position", buf, sizeof(buf), hwnd);
    ok(!err, "status position: %s\n", dbg_mcierr(err));
    if(!err) todo_wine ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
                "status position initially %s\n", buf);
    /* 2 seconds is the initial position even with data tracks. */

    err = mciSendString("status c position start notify", buf, sizeof(buf), hwnd);
    ok(err == ok_hw, "status position start: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
                "status position start %s\n", buf);
    test_notification(hwnd, "status notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);

    err = mciSendString("status c position start track 1 notify", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start: %s\n", dbg_mcierr(err));
    test_notification(hwnd, "status 2flags", err ? 0 : MCI_NOTIFY_SUCCESSFUL);

    err = mciSendString("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd);
    todo_wine ok(err == MCIERR_OUTOFRANGE, "play 2s to 1s: %s\n", dbg_mcierr(err));
    test_notification(hwnd, "play 2s to 1s", err ? 0 : MCI_NOTIFY_SUCCESSFUL);

    err = mciSendString("resume c", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_HARDWARE || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
       "resume without play: %s\n", dbg_mcierr(err)); /* not NONAPPLICABLE_FUNCTION */
    /* vmware with a .iso (data-only) yields no error on NT/w2k */

    err = mciSendString("seek c wait", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err));

    err = mciSendString("seek c to start to end", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE || broken(!err), "seek to start+end: %s\n", dbg_mcierr(err));
    /* Win9x only errors out with Seek to start to <position> */

    /* set Wine to a defined position before play */
    err = mciSendString("seek c to start notify", buf, sizeof(buf), hwnd);
    ok(!err, "seek to start: %s\n", dbg_mcierr(err));
    test_notification(hwnd, "seek to start", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
    /* Win9X Status position / current track then sometimes report the end position / track! */

    err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
    ok(!err, "status mode: %s\n", dbg_mcierr(err));
    if(!err) ok(!strcmp(buf, "stopped"), "status mode after seek is %s\n", buf);

    /* MCICDA ignores MCI_SET_VIDEO
     * One xp machine ignored SET_AUDIO, one w2k and one w7 machine honoured it
     * and simultaneously toggled the mute button in the mixer control panel.
     * Or does it only depend on the HW, not the OS? */
    err = mciSendString("set c video audio all on", buf, sizeof(buf), hwnd);
    ok(!err, "set video/audio: %s\n", dbg_mcierr(err));

    err = mciSendString("set c time format ms", buf, sizeof(buf), hwnd);
    ok(!err, "set time format ms: %s\n", dbg_mcierr(err));

    memset(buf, 0, sizeof(buf));
    err = mciSendString("status c position start", buf, sizeof(buf), hwnd);
    ok(!err, "status position start (ms): %s\n", dbg_mcierr(err));
    duration = atoi(buf);
    if(!err) ok(duration > 2000, "status position initially %sms\n", buf);
    /* 00:02:00 corresponds to 2001 ms, 02:33 -> 2441 etc. */

    err = mciSendString("status c position start track 1", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start+track: %s\n", dbg_mcierr(err));

    err = mciSendString("status c notify wait", buf, sizeof(buf), hwnd);
    ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err));

    err = mciSendString("status c length track 1", buf, sizeof(buf), hwnd);
    ok(!err, "status length (ms): %s\n", dbg_mcierr(err));
    if(!err) {
        trace("track #1 length %sms\n", buf);
        duration = atoi(buf);
    } else duration = 2001; /* for the position test below */

    if (0) { /* causes some native systems to return Seek and Play with MCIERR_HARDWARE */
        /* depending on capability can eject only? */
        err = mciSendString("set c door closed notify", buf, sizeof(buf), hwnd);
        ok(!err, "set door closed: %s\n", dbg_mcierr(err));
        test_notification(hwnd, "door closed", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
    }
    /* Changing the disk while the MCI device is open causes the Status
     * command to report stale data.  Native obviously caches the TOC. */

    /* status type track is localised, strcmp("audio|other") may fail. */
    parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
    parm.status.dwTrack = 1;
    parm.status.dwReturn = 0xFEEDABAD;
    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
    ok(!err, "STATUS type track 1: %s\n", dbg_mcierr(err));
    ok(parm.status.dwReturn==MCI_CDA_TRACK_OTHER || parm.status.dwReturn==MCI_CDA_TRACK_AUDIO,
       "unknown track type %lx\n", parm.status.dwReturn);

    if (parm.status.dwReturn == MCI_CDA_TRACK_OTHER) {
        /* Find an audio track */
        parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
        parm.status.dwTrack = numtracks;
        parm.status.dwReturn = 0xFEEDABAD;
        err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
        ok(!err, "STATUS type track %u: %s\n", numtracks, dbg_mcierr(err));
        ok(parm.status.dwReturn == MCI_CDA_TRACK_OTHER || parm.status.dwReturn == MCI_CDA_TRACK_AUDIO,
           "unknown track type %lx\n", parm.status.dwReturn);
        track = (!err && parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) ? numtracks : 0;

        /* Seek to start (above) skips over data tracks
         * In case of a data only CD, it seeks to the end of disk, however
         * another Status position a few seconds later yields MCIERR_HARDWARE. */
        parm.status.dwItem = MCI_STATUS_POSITION;
        parm.status.dwReturn = 2000;
        err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
        ok(!err || broken(err == MCIERR_HARDWARE), "STATUS position: %s\n", dbg_mcierr(err));

        if(!err && track) ok(parm.status.dwReturn > duration,
            "Seek did not skip data tracks, position %lums\n", parm.status.dwReturn);
        /* dwReturn > start + length(#1) may fail because of small position report fluctuation.
         * On some native systems, status position fluctuates around the target position;
         * Successive calls return varying positions! */

        err = mciSendString("set c time format msf", buf, sizeof(buf), hwnd);
        ok(!err, "set time format msf: %s\n", dbg_mcierr(err));

        parm.status.dwItem = MCI_STATUS_LENGTH;
        parm.status.dwTrack = 1;
        parm.status.dwReturn = 0xFEEDABAD;
        err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
        ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
        duration = parm.status.dwReturn;
        trace("track #1 length: %02um:%02us:%02uframes\n",
              MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
        ok(duration>>24==0, "CD length high bits %08X\n", duration);

        /* TODO only with mixed CDs? */
        /* play track 1 to length silently works with data tracks */
        parm.play.dwFrom = MCI_MAKE_MSF(0,2,0);
        parm.play.dwTo = duration; /* omitting 2 seconds from end */
        err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO, (DWORD_PTR)&parm);
        ok(!err, "PLAY data to %08X: %s\n", duration, dbg_mcierr(err));

        Sleep(1500*factor); /* Time to spin up, hopefully less than track length */

        err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
        ok(!err, "status mode: %s\n", dbg_mcierr(err));
        if(!err) ok(!strcmp(buf, "stopped"), "status mode on data is %s\n", buf);
    } else if (parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) {
Ejemplo n.º 6
0
MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR fdwCommand, DWORD_PTR dwParam)
{
    char cmdbuf[1024];

    dprintf("mciSendCommandA(IDDevice=%p, uMsg=%p, fdwCommand=%p, dwParam=%p)\r\n", IDDevice, uMsg, fdwCommand, dwParam);

    if (fdwCommand & MCI_NOTIFY)
    {
        dprintf("  MCI_NOTIFY\r\n");
    }

    if (fdwCommand & MCI_WAIT)
    {
        dprintf("  MCI_WAIT\r\n");
    }

    if (uMsg == MCI_OPEN)
    {
        LPMCI_OPEN_PARMS parms = (LPVOID)dwParam;

        dprintf("  MCI_OPEN\r\n");

        if (fdwCommand & MCI_OPEN_ALIAS)
        {
            dprintf("    MCI_OPEN_ALIAS\r\n");
        }

        if (fdwCommand & MCI_OPEN_SHAREABLE)
        {
            dprintf("    MCI_OPEN_SHAREABLE\r\n");
        }

        if (fdwCommand & MCI_OPEN_TYPE_ID)
        {
            dprintf("    MCI_OPEN_TYPE_ID\r\n");

            if (LOWORD(parms->lpstrDeviceType) == MCI_DEVTYPE_CD_AUDIO)
            {
                dprintf("  Returning magic device id for MCI_DEVTYPE_CD_AUDIO\r\n");
                parms->wDeviceID = MAGIC_DEVICEID;
                return 0;
            }
        }

        if (fdwCommand & MCI_OPEN_TYPE && !(fdwCommand & MCI_OPEN_TYPE_ID))
        {
            dprintf("    MCI_OPEN_TYPE\r\n");
            dprintf("        -> %s\r\n", parms->lpstrDeviceType);

            if (strcmp(parms->lpstrDeviceType, "cdaudio") == 0)
            {
                dprintf("  Returning magic device id for MCI_DEVTYPE_CD_AUDIO\r\n");
                parms->wDeviceID = MAGIC_DEVICEID;
                return 0;
            }
        }

    }

    if (IDDevice == MAGIC_DEVICEID || IDDevice == 0 || IDDevice == 0xFFFFFFFF)
    {
        if (uMsg == MCI_SET)
        {
            LPMCI_SET_PARMS parms = (LPVOID)dwParam;

            dprintf("  MCI_SET\r\n");

            if (fdwCommand & MCI_SET_TIME_FORMAT)
            {
                dprintf("    MCI_SET_TIME_FORMAT\r\n");

                time_format = parms->dwTimeFormat;

                if (parms->dwTimeFormat == MCI_FORMAT_BYTES)
                {
                    dprintf("      MCI_FORMAT_BYTES\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_FRAMES)
                {
                    dprintf("      MCI_FORMAT_FRAMES\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_HMS)
                {
                    dprintf("      MCI_FORMAT_HMS\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_MILLISECONDS)
                {
                    dprintf("      MCI_FORMAT_MILLISECONDS\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_MSF)
                {
                    dprintf("      MCI_FORMAT_MSF\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_SAMPLES)
                {
                    dprintf("      MCI_FORMAT_SAMPLES\r\n");
                }

                if (parms->dwTimeFormat == MCI_FORMAT_TMSF)
                {
                    dprintf("      MCI_FORMAT_TMSF\r\n");
                }
            }
        }

        if (uMsg == MCI_CLOSE)
        {
            dprintf("  MCI_CLOSE\r\n");

            if (player)
            {
                TerminateThread(player, 0);
            }

            playing = 0;
            player = NULL;
        }

        if (uMsg == MCI_PLAY)
        {
            LPMCI_PLAY_PARMS parms = (LPVOID)dwParam;
            static struct play_info info = { -1, -1 };

            dprintf("  MCI_PLAY\r\n");

            if (fdwCommand & MCI_FROM)
            {
                dprintf("    dwFrom: %d\r\n", parms->dwFrom);

                // FIXME: rounding to nearest track
                if (time_format == MCI_FORMAT_TMSF)
                {
                    info.first = MCI_TMSF_TRACK(parms->dwFrom);

                    dprintf("      TRACK  %d\n", MCI_TMSF_TRACK(parms->dwFrom));
                    dprintf("      MINUTE %d\n", MCI_TMSF_MINUTE(parms->dwFrom));
                    dprintf("      SECOND %d\n", MCI_TMSF_SECOND(parms->dwFrom));
                    dprintf("      FRAME  %d\n", MCI_TMSF_FRAME(parms->dwFrom));
                }
                else if (time_format == MCI_FORMAT_MILLISECONDS)
                {
                    info.first = 0;

                    for (int i = 0; i < MAX_TRACKS; i++)
                    {
                        // FIXME: take closest instead of absolute
                        if (tracks[i].position == parms->dwFrom / 1000)
                        {
                            info.first = i;
                        }
                    }

                    dprintf("      mapped milliseconds to %d\n", info.first);
                }
                else
                {
                    // FIXME: not really
                    info.first = parms->dwFrom;
                }

                if (info.first < firstTrack)
                    info.first = firstTrack;

                if (info.first > lastTrack)
                    info.first = lastTrack;

                info.last = info.first;
            }

            if (fdwCommand & MCI_TO)
            {
                dprintf("    dwTo:   %d\r\n", parms->dwTo);

                if (time_format == MCI_FORMAT_TMSF)
                {
                    info.last = MCI_TMSF_TRACK(parms->dwTo);

                    dprintf("      TRACK  %d\n", MCI_TMSF_TRACK(parms->dwTo));
                    dprintf("      MINUTE %d\n", MCI_TMSF_MINUTE(parms->dwTo));
                    dprintf("      SECOND %d\n", MCI_TMSF_SECOND(parms->dwTo));
                    dprintf("      FRAME  %d\n", MCI_TMSF_FRAME(parms->dwTo));
                }
                else if (time_format == MCI_FORMAT_MILLISECONDS)
                {
                    info.last = info.first;

                    for (int i = info.first; i < MAX_TRACKS; i ++)
                    {
                        // FIXME: use better matching
                        if (tracks[i].position + tracks[i].length > parms->dwFrom / 1000)
                        {
                            info.last = i;
                            break;
                        }
                    }

                    dprintf("      mapped milliseconds to %d\n", info.last);
                }
                else
                    info.last = parms->dwTo;

                if (info.last < info.first)
                    info.last = info.first;

                if (info.last > lastTrack)
                    info.last = lastTrack;
            }

            if (info.first && (fdwCommand & MCI_FROM))
            {
                if (player)
                {
                    TerminateThread(player, 0);
                }

                playing = 0;
                player = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)player_main, (void *)&info, 0, NULL);
            }
        }

        if (uMsg == MCI_STOP)
        {
            dprintf("  MCI_STOP\r\n");
            playing = 0;
        }

        if (uMsg == MCI_STATUS)
        {
            LPMCI_STATUS_PARMS parms = (LPVOID)dwParam;

            dprintf("  MCI_STATUS\r\n");

            parms->dwReturn = 0;

            if (fdwCommand & MCI_TRACK)
            {
                dprintf("    MCI_TRACK\r\n");
                dprintf("      dwTrack = %d\r\n", parms->dwTrack);
            }

            if (fdwCommand & MCI_STATUS_ITEM)
            {
                dprintf("    MCI_STATUS_ITEM\r\n");

                if (parms->dwItem == MCI_STATUS_CURRENT_TRACK)
                {
                    dprintf("      MCI_STATUS_CURRENT_TRACK\r\n");
                }

                if (parms->dwItem == MCI_STATUS_LENGTH)
                {
                    dprintf("      MCI_STATUS_LENGTH\r\n");

                    int seconds = tracks[parms->dwTrack].length;

                    if (seconds)
                    {
                        if (time_format == MCI_FORMAT_MILLISECONDS)
                        {
                            parms->dwReturn = seconds * 1000;
                        }
                        else
                        {
                            parms->dwReturn = MCI_MAKE_MSF(seconds / 60, seconds % 60, 0);
                        }
                    }
                }

                if (parms->dwItem == MCI_CDA_STATUS_TYPE_TRACK)
                {
                    dprintf("      MCI_CDA_STATUS_TYPE_TRACK\r\n");
                }

                if (parms->dwItem == MCI_STATUS_MEDIA_PRESENT)
                {
                    dprintf("      MCI_STATUS_MEDIA_PRESENT\r\n");
                    parms->dwReturn = lastTrack > 0;
                }

                if (parms->dwItem == MCI_STATUS_NUMBER_OF_TRACKS)
                {
                    dprintf("      MCI_STATUS_NUMBER_OF_TRACKS\r\n");
                    parms->dwReturn = numTracks;
                }

                if (parms->dwItem == MCI_STATUS_POSITION)
                {
                    dprintf("      MCI_STATUS_POSITION\r\n");

                    if (fdwCommand & MCI_TRACK)
                    {
                        // FIXME: implying milliseconds
                        parms->dwReturn = tracks[parms->dwTrack].position * 1000;
                    }
                }

                if (parms->dwItem == MCI_STATUS_MODE)
                {
                    dprintf("      MCI_STATUS_MODE\r\n");
                    dprintf("        we are %s\r\n", playing ? "playing" : "NOT playing");

                    parms->dwReturn = playing ? MCI_MODE_PLAY : MCI_MODE_STOP;
                }

                if (parms->dwItem == MCI_STATUS_READY)
                {
                    dprintf("      MCI_STATUS_READY\r\n");
                }

                if (parms->dwItem == MCI_STATUS_TIME_FORMAT)
                {
                    dprintf("      MCI_STATUS_TIME_FORMAT\r\n");
                }

                if (parms->dwItem == MCI_STATUS_START)
                {
                    dprintf("      MCI_STATUS_START\r\n");
                }
            }

            dprintf("  dwReturn %d\n", parms->dwReturn);

        }

        return 0;
    }

    /* fallback */
    return MCIERR_UNRECOGNIZED_COMMAND;
}