Skip to content

Commit

Permalink
Implement time range playing, pause and resume.
Browse files Browse the repository at this point in the history
  • Loading branch information
ayuanx committed Feb 25, 2023
1 parent 0d193d7 commit 51140de
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 56 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ ogg-winmm also provides separate volume control for CDDA/MIDI/WAVE, which has be

# Revisions:

v.2023.02.26:
- Implement time range playing down to seconds instead of always track-wise playing.
- Implement pause and resume.

v.2023.01.18:
- Implement play/seek/length/position for mciSendString.

Expand Down
118 changes: 68 additions & 50 deletions ogg-winmm.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#define MAGIC_DEVICEID 0xCDDA
#define MAX_TRACKS 99

//#define _DEBUG

#ifdef _DEBUG
#define dprintf(...) if (fh) { fprintf(fh, __VA_ARGS__); fflush(NULL); }
FILE *fh = NULL;
Expand All @@ -43,7 +45,9 @@ struct track_info
struct play_info
{
int first;
unsigned int from; /* seconds */
int last;
unsigned int to; /* seconds */
};

static struct track_info tracks[MAX_TRACKS];
Expand All @@ -56,8 +60,8 @@ const char alias_def[] = "cdaudio";
char alias_s[100] = "cdaudio";
char music_path[MAX_PATH];

int mode = MCI_MODE_STOP;
int command = 0;
int playing = 0;
int notify = 0;
int current = 0;
int firstTrack = 0;
Expand All @@ -70,46 +74,40 @@ int cddaVol = -1;
int midiVol = -1;
int waveVol = -1;

/* NOTE: The player is currently incapable of playing tracks from a specified
* position. Instead it plays whole tracks only. Previous pause logic using
* Sleep caused crackling sound and was not acceptable. Future plan is to add
* proper seek commands to support playback from arbitrary positions on the track.
*/

int WINAPI player_main(void *unused)
{
while (WaitForSingleObject(event, INFINITE) == 0) {
int first = info.first < firstTrack ? firstTrack : info.first;
int last = info.last - 1 < first ? first : info.last - 1; /* -1 for plr logic */
int last = info.last > lastTrack ? lastTrack : info.last;
unsigned int from = info.from, to = info.to;
current = first;
dprintf("[Thread] From %d to %d\n", first, last);
dprintf("[Thread] From %d (%d sec) to %d (%d sec)\n", first, from, last, to);

while (command == MCI_PLAY && current <= last) {
dprintf("[Thread] Current track %s\n", tracks[current].path);
tracks[current].tick = clock();
playing = 1;
plr_play(tracks[current].path);
mode = MCI_MODE_PLAY;
plr_play(tracks[current].path, current == first ? from : 0, current == last ? to : 0);

while (command == MCI_PLAY) {
if (plr_pump() == 0) {
int more = plr_pump();
if (more == 0) {
current++;
break;
} else if (more < 0) {
break;
}

}
}

/* Sending notify successful message:*/
if (command == MCI_PLAY && notify) {
notify = 0;
SendNotifyMessageA(window, MM_MCINOTIFY, MCI_NOTIFY_SUCCESSFUL, MAGIC_DEVICEID);
/* NOTE: Notify message after successful playback is not working in Vista+.
MCI_STATUS_MODE does not update to show that the track is no longer playing.
Bug or broken design in mcicda.dll (also noted by the Wine team) */
dprintf("[Thread] Send MCI_NOTIFY_SUCCESSFUL message\n");
}

playing = 0;
mode = MCI_MODE_STOP;
plr_reset();
if (command == MCI_DELETE) break;
}
Expand Down Expand Up @@ -262,14 +260,24 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
case MCI_PLAY:
{
dprintf(" MCI_PLAY\n");
if (mode == MCI_MODE_PLAY) {
dprintf(" already playing\n");
break;
} else if (mode == MCI_MODE_PAUSE) {
dprintf(" resume instead of new play\n");
mode = MCI_MODE_PLAY;
plr_resume();
break;
}

LPMCI_PLAY_PARMS parms = (LPVOID)dwParam;

if (fdwCommand & MCI_FROM) {
dprintf(" dwFrom: 0x%08X\n", parms->dwFrom);

/* FIXME: rounding to nearest track */
if (time_format == MCI_FORMAT_TMSF) {
info.first = MCI_TMSF_TRACK(parms->dwFrom);
info.from = MCI_TMSF_MINUTE(parms->dwFrom) * 60 + MCI_TMSF_SECOND(parms->dwFrom);

dprintf(" TRACK %d\n", MCI_TMSF_TRACK(parms->dwFrom));
dprintf(" MINUTE %d\n", MCI_TMSF_MINUTE(parms->dwFrom));
Expand All @@ -285,6 +293,7 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
for (int i = firstTrack; i <= lastTrack; i++) {
if (tracks[i].position + tracks[i].length > parms->dwFrom) {
info.first = i;
info.from = parms->dwFrom - tracks[i].position;
break;
}
}
Expand All @@ -294,50 +303,53 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
plr_stop();
return 0;
}
dprintf(" mapped dwFrom to track %d\n", info.first);
dprintf(" mapped dwFrom to track %d (%d sec)\n", info.first, info.from);
}
if (info.first < firstTrack) info.first = firstTrack;
else if (info.first > lastTrack) info.first = lastTrack;
info.last = lastTrack; /* default MCI_TO */
info.to = -1;
}

if (fdwCommand & MCI_TO) {
dprintf(" dwTo: 0x%08X\n", parms->dwTo);

if (time_format == MCI_FORMAT_TMSF) {
info.last = MCI_TMSF_TRACK(parms->dwTo);
info.to = MCI_TMSF_MINUTE(parms->dwTo) * 60 + MCI_TMSF_SECOND(parms->dwTo) + (MCI_TMSF_FRAME(parms->dwTo) ? 1 : 0);

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 { /* MSF or Milliseconds */
if (time_format == MCI_FORMAT_MSF) {
parms->dwTo = MCI_MSF_MINUTE(parms->dwTo) * 60 + MCI_MSF_SECOND(parms->dwTo);
parms->dwTo = MCI_MSF_MINUTE(parms->dwTo) * 60 + MCI_MSF_SECOND(parms->dwTo) + (MCI_MSF_FRAME(parms->dwTo) ? 1 : 0);
} else {
parms->dwTo /= 1000;
}
info.last = lastTrack;
info.to = -1;
for (int i = info.first; i <= lastTrack; i++) {
if (tracks[i].position + tracks[i].length >= parms->dwTo) {
info.last = i;
info.to = parms->dwTo - tracks[i].position;
break;
}
}
dprintf(" mapped dwTo to track %d\n", info.last);
dprintf(" mapped dwTo to track %d (%d sec)\n", info.last, info.to);
}
if (!info.to) { // Convert track range from [) to []
info.last--;
info.to = -1;
}
if (info.last < info.first) info.last = info.first;
else if (info.last > lastTrack) info.last = lastTrack;
}

if ((info.first && (fdwCommand & MCI_FROM)) || (info.last && (fdwCommand & MCI_TO))) {
if (playing) {
command = MCI_STOP;
plr_stop();
while (playing) {
Sleep(0);
}
if ((fdwCommand & MCI_FROM) && (fdwCommand & MCI_TO) && (parms->dwFrom == parms->dwTo)) {
if (notify) {
notify = 0;
SendNotifyMessageA(window, MM_MCINOTIFY, MCI_NOTIFY_SUCCESSFUL, MAGIC_DEVICEID);
dprintf("(FROM == TO) Send message but no play\n");
}
} else if ((info.first && (fdwCommand & MCI_FROM)) || (info.last && (fdwCommand & MCI_TO))) {
if (event) {
command = MCI_PLAY;
SetEvent(event);
Expand All @@ -352,11 +364,11 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
plr_stop(); /* Make STOP command instant. */
}
break;
case MCI_PAUSE: /* FIXME: MCICDA does not support resume, so pause should be equivalent to stop */
case MCI_PAUSE: /* FIXME: MCICDA does not support resume? */
{
dprintf(" MCI_PAUSE\n");
command = MCI_STOP;
plr_stop();
plr_pause();
mode = MCI_MODE_PAUSE;
}
break;
case MCI_INFO: /* Handling of MCI_INFO */
Expand Down Expand Up @@ -473,7 +485,8 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
parms->dwReturn = 0;
}
} else { /* Playing position */
unsigned int ms = playing ? (clock() - tracks[current].tick) * 1000 / CLOCKS_PER_SEC : 0;
// FIXME: fix position for pause
unsigned int ms = mode == MCI_MODE_PLAY ? (clock() - tracks[current].tick) * 1000 / CLOCKS_PER_SEC : 0;
if (time_format == MCI_FORMAT_MILLISECONDS) {
parms->dwReturn = ms;
} else if (time_format == MCI_FORMAT_MSF) {
Expand All @@ -490,8 +503,7 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
break;
case MCI_STATUS_MODE:
dprintf(" MCI_STATUS_MODE\n");
dprintf(" we are %s\n", playing ? "playing" : "NOT playing");
parms->dwReturn = playing ? MCI_MODE_PLAY : MCI_MODE_STOP;
parms->dwReturn = mode;
break;
case MCI_STATUS_MEDIA_PRESENT:
dprintf(" MCI_STATUS_MEDIA_PRESENT\n");
Expand All @@ -518,12 +530,12 @@ MCIERROR WINAPI fake_mciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR
dprintf(" dwReturn 0x%08X\n", parms->dwReturn);
}
break;
case MCI_RESUME: /* FIXME: MCICDA does not support resume, so resume should be equivalent to play */
case MCI_RESUME: /* FIXME: MCICDA does not support resume? */
{
dprintf(" MCI_RESUME\n");
if (event) {
command = MCI_PLAY;
SetEvent(event);
if (mode == MCI_MODE_PAUSE) {
mode = MCI_MODE_PLAY;
plr_resume();
}
}
break;
Expand Down Expand Up @@ -729,13 +741,19 @@ MCIERROR WINAPI fake_mciSendStringA(LPCSTR cmd, LPSTR ret, UINT cchReturn, HANDL
/* Add: Mode handling */
if (strstr(cmdbuf, "mode"))
{
if (playing) {
dprintf(" -> playing\n");
strcpy(ret, "playing");
}
else{
dprintf(" -> stopped\n");
strcpy(ret, "stopped");
switch (mode) {
case MCI_MODE_PLAY:
dprintf(" -> playing\n");
strcpy(ret, "playing");
break;
case MCI_MODE_PAUSE:
dprintf(" -> paused\n");
strcpy(ret, "paused");
break;
default:
dprintf(" -> stopped\n");
strcpy(ret, "stopped");
break;
}
return 0;
}
Expand All @@ -745,7 +763,7 @@ MCIERROR WINAPI fake_mciSendStringA(LPCSTR cmd, LPSTR ret, UINT cchReturn, HANDL
int from = -1, to = -1;
sprintf(cmp_str, "play %s", alias_s);
if (strstr(cmdbuf, cmp_str)){
MCI_PLAY_PARMS parms;
MCI_PLAY_PARMS parms = {0};

if (strstr(cmdbuf, "notify")){
notify = 1; /* storing the notify request */
Expand Down
26 changes: 21 additions & 5 deletions player.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#include <vorbis/vorbisfile.h>

#define WAV_BUF_CNT (2) // Dual buffer
#define WAV_BUF_LEN (48000*2) // 48000Hz, 16-bit, 2-channel, 1/2 second buffer
#define WAV_BUF_LEN (44100*2) // 44100Hz, 16-bit, 2-channel, 1/2 second buffer

bool plr_run = false;
bool plr_bsy = false;
unsigned int plr_len = 0;
float plr_vol = -1.0;

HWAVEOUT plr_hw = NULL;
Expand Down Expand Up @@ -59,7 +60,7 @@ void plr_reset()
}
}

int plr_play(const char *path)
int plr_play(const char *path, unsigned int from, unsigned int to)
{
plr_reset();

Expand All @@ -84,6 +85,7 @@ int plr_play(const char *path)
ov_clear(&plr_vf);
CloseHandle(plr_ev);
plr_ev = NULL;
plr_hw = NULL;
return 0;
}

Expand All @@ -93,6 +95,9 @@ int plr_play(const char *path)
plr_hdr[i].dwFlags = WHDR_DONE;
}

if (from) ov_time_seek(&plr_vf, (double)from);
plr_len = to > from ? (to - from) * vi->rate * 2 * vi->channels : -1;

plr_run = true;
return 1;
}
Expand All @@ -111,15 +116,25 @@ void plr_stop()
}
}

void plr_pause()
{
if (plr_hw) waveOutPause(plr_hw);
}

void plr_resume()
{
if (plr_hw) waveOutRestart(plr_hw);
}

int plr_pump()
{
if (!plr_run || !plr_vf.datasource) return 0;
if (!plr_run || !plr_vf.datasource) return -1;

plr_bsy = true;

if (WaitForSingleObject(plr_ev, INFINITE) != 0 || !plr_run) {
plr_bsy = false;
return 0;
return -1;
}

for (int n = 0, i = plr_que; n < WAV_BUF_CNT; n++, i = (i+1) % WAV_BUF_CNT) {
Expand All @@ -133,7 +148,7 @@ int plr_pump()
}

char *buf = plr_buf[i];
int pos = 0, size = plr_fmt.nSamplesPerSec * 2;
unsigned int pos = 0, size = plr_len > WAV_BUF_LEN ? WAV_BUF_LEN : plr_len;
while (pos < size) {
long bytes = ov_read(&plr_vf, buf + pos, size - pos, 0, 2, 1, NULL);

Expand All @@ -154,6 +169,7 @@ int plr_pump()
plr_bsy = false;
return 0;
}
plr_len -= pos;

/* volume control, kinda nasty */
if (plr_vol != -1) {
Expand Down
4 changes: 3 additions & 1 deletion player.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
void plr_volume(int vol);
void plr_reset();
void plr_stop();
void plr_pause();
void plr_resume();
int plr_pump();
int plr_play(const char *path);
int plr_play(const char *path, unsigned int from, unsigned int to);
int plr_length(const char *path);

0 comments on commit 51140de

Please sign in to comment.