From 08cec5ec1cdec3a16669f40b04c79aead32ebeac Mon Sep 17 00:00:00 2001 From: Toni Wilen Date: Sat, 21 Sep 2019 10:36:51 +0300 Subject: [PATCH] SPTI CD/DVD mode emulated CD audio. --- blkdev.cpp | 99 ++++++- include/blkdev.h | 3 + od-win32/blkdev_win32_ioctl.cpp | 450 ++++---------------------------- od-win32/blkdev_win32_spti.cpp | 246 +++++++++++++++-- od-win32/cda_play.cpp | 369 ++++++++++++++++++++++++++ od-win32/cda_play.h | 36 +++ 6 files changed, 784 insertions(+), 419 deletions(-) diff --git a/blkdev.cpp b/blkdev.cpp index 1646ee37..7005e05d 100644 --- a/blkdev.cpp +++ b/blkdev.cpp @@ -880,7 +880,7 @@ int sys_command_cd_qcode (int unitnum, uae_u8 *buf, int sector, bool all) }; /* read table of contents */ -int sys_command_cd_toc (int unitnum, struct cd_toc_head *toc) +int sys_command_cd_toc (int unitnum, struct cd_toc_head *th) { int v; if (failunit (unitnum)) @@ -890,14 +890,37 @@ int sys_command_cd_toc (int unitnum, struct cd_toc_head *toc) if (state[unitnum].device_func->toc == NULL) { uae_u8 buf[4 + 8 * 103]; int size = sizeof buf; - uae_u8 cmd [10] = { 0x43,0,2,0,0,0,0,(uae_u8)(size>>8),(uae_u8)(size&0xff),0}; - if (do_scsi (unitnum, cmd, sizeof cmd, buf, size)) { - // toc parse to do - v = 0; + uae_u8 cmd [10] = { 0x43,0,0,0,0,0,0,(uae_u8)(size>>8),(uae_u8)(size&0xff),0}; + v = do_scsi(unitnum, cmd, sizeof cmd, buf, size); + if (v > 0) { + th->first_track = buf[2]; + th->last_track = buf[3]; + th->tracks = th->last_track - th->first_track + 1; + th->firstaddress = 0; + th->points = th->tracks + 1; + int len = (buf[0] << 8) | buf[1]; + uae_u8 *p = buf + 4; + if ((th->tracks + 1) * 8 > len) { + freesem(unitnum); + return 0; + } + struct cd_toc *t = &th->toc[th->first_track]; + for (int i = th->first_track; i <= th->last_track; i++) { + t->adr = p[1] >> 4; + t->control = p[1] & 15; + t->point = t->track = p[2]; + t->paddress = (p[5] << 16) | (p[6] << 8) | (p[7] << 0); + p += 8; + t++; + } + th->lastaddress = (p[5] << 16) | (p[6] << 8) | (p[7] << 0); + t->track = t->point = 0xaa; + th->first_track_offset = 0; + th->last_track_offset = th->last_track; + v = 1; } - v = 0; } else { - v = state[unitnum].device_func->toc (unitnum, toc); + v = state[unitnum].device_func->toc (unitnum, th); } freesem (unitnum); return v; @@ -2160,6 +2183,62 @@ end: return status; } +static int emulate_cd_audio = 1; + +int blkdev_is_audio_command(uae_u8 cmd) +{ + if (!emulate_cd_audio) + return false; + // audio cd command? + switch (cmd) + { + case 0x42: + case 0x45: + case 0x47: + case 0x48: + case 0x49: + case 0x4b: + case 0x4e: + case 0xa5: + case 0xa9: + case 0xba: + case 0xbc: + case 0xcd: + return 1; + } + + // commands that won't stop cd audio + switch (cmd) + { + case 0x00: + case 0x03: + case 0x12: + case 0x15: + case 0x25: + case 0x35: + case 0x1a: + case 0x1e: + case 0x55: + case 0x5a: + return 0; + } + + // all other commands stop cd audio + return -1; +} + + +int blkdev_execute_audio_command(int unitnum, uae_u8 *cdb, int cdblen, uae_u8 *inbuf, int inlen, uae_u8 *sense, int *senselen) +{ + int len = inlen; + uae_u8 reply[256]; + int replylen = sizeof(reply); + int status = scsi_cd_emulate(unitnum, cdb, cdblen, inbuf, &len, reply, &replylen, sense, senselen, true); + if (status) + return -1; + return len; +} + static int execscsicmd_direct (int unitnum, int type, struct amigascsi *as) { int io_error = 0; @@ -2276,6 +2355,12 @@ int sys_command_scsi_direct(TrapContext *ctx, int unitnum, int type, uaecptr acm get_long_host(scsicmd + 12), as.cmd_len, as.flags, get_long_host(scsicmd + 22), as.sense_len); + for (int i = 0; i < as.cmd_len; i++) { + if (i > 0) + write_log(_T(".")); + write_log(_T("%02x"), as.cmd[i]); + } + write_log(_T("\n")); } ret = sys_command_scsi_direct_native (unitnum, type, &as); diff --git a/include/blkdev.h b/include/blkdev.h index 11246754..d27456dd 100644 --- a/include/blkdev.h +++ b/include/blkdev.h @@ -227,4 +227,7 @@ extern struct device_functions devicefunc_scsi_ioctl; extern struct device_functions devicefunc_scsi_spti; extern struct device_functions devicefunc_cdimage; +int blkdev_is_audio_command(uae_u8 cmd); +int blkdev_execute_audio_command(int unitnum, uae_u8 *cdb, int cdblen, uae_u8 *inbuf, int inlen, uae_u8 *sense, int *senselen); + #endif /* UAE_BLKDEV_H */ diff --git a/od-win32/blkdev_win32_ioctl.cpp b/od-win32/blkdev_win32_ioctl.cpp index 0598ee12..90654e37 100644 --- a/od-win32/blkdev_win32_ioctl.cpp +++ b/od-win32/blkdev_win32_ioctl.cpp @@ -12,8 +12,6 @@ #ifdef WINDDK -#include - #include "options.h" #include "traps.h" #include "uae.h" @@ -40,7 +38,6 @@ #include #define IOCTL_DATA_BUFFER 8192 -#define CDDA_BUFFERS 14 struct dev_info_ioctl { HANDLE h; @@ -52,31 +49,12 @@ struct dev_info_ioctl { CDROM_TOC cdromtoc; uae_u8 trackmode[100]; UINT errormode; - int playend; int fullaccess; - int cdda_play_finished; - int cdda_play; - int cdda_paused; - int cdda_volume[2]; - int cdda_scan; - int cd_last_pos; - HWAVEOUT cdda_wavehandle; - int cdda_start, cdda_end; - uae_u8 subcode[SUB_CHANNEL_SIZE * CDDA_BUFFERS]; - uae_u8 subcodebuf[SUB_CHANNEL_SIZE]; - bool subcodevalid; - play_subchannel_callback cdda_subfunc; - play_status_callback cdda_statusfunc; - int cdda_play_state; - int cdda_delay, cdda_delay_frames; struct device_info di; - uae_sem_t sub_sem, sub_sem2; bool open; bool usesptiread; bool changed; - cda_audio *cda; - volatile int cda_bufon[2]; - struct cd_audio_state cas; + struct cda_play cda; }; static struct dev_info_ioctl ciw32[MAX_TOTAL_SCSI_DEVICES]; @@ -451,333 +429,9 @@ retry: return ret; } -static void cdda_closewav (struct dev_info_ioctl *ciw) -{ - if (ciw->cdda_wavehandle != NULL) - waveOutClose (ciw->cdda_wavehandle); - ciw->cdda_wavehandle = NULL; -} - -// DAE CDDA based on Larry Osterman's "Playing Audio CDs" blog series - -static int cdda_openwav (struct dev_info_ioctl *ciw) -{ - WAVEFORMATEX wav = { 0 }; - MMRESULT mmr; - - wav.cbSize = 0; - wav.nChannels = 2; - wav.nSamplesPerSec = 44100; - wav.wBitsPerSample = 16; - wav.nBlockAlign = wav.wBitsPerSample / 8 * wav.nChannels; - wav.nAvgBytesPerSec = wav.nBlockAlign * wav.nSamplesPerSec; - wav.wFormatTag = WAVE_FORMAT_PCM; - mmr = waveOutOpen (&ciw->cdda_wavehandle, WAVE_MAPPER, &wav, 0, 0, WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT); - if (mmr != MMSYSERR_NOERROR) { - write_log (_T("IOCTL CDDA: wave open %d\n"), mmr); - cdda_closewav (ciw); - return 0; - } - return 1; -} - -static int setstate (struct dev_info_ioctl *ciw, int state, int playpos) -{ - ciw->cdda_play_state = state; - if (ciw->cdda_statusfunc) - return ciw->cdda_statusfunc (ciw->cdda_play_state, playpos); - return 0; -} - -void ioctl_next_cd_audio_buffer_callback(int bufnum, void *param) -{ - struct dev_info_ioctl *ciw = (struct dev_info_ioctl*)param; - uae_sem_wait(&play_sem); - if (bufnum >= 0) { - ciw->cda_bufon[bufnum] = 0; - bufnum = 1 - bufnum; - if (ciw->cda_bufon[bufnum]) - audio_cda_new_buffer(&ciw->cas, (uae_s16*)ciw->cda->buffers[bufnum], CDDA_BUFFERS * 2352 / 4, bufnum, ioctl_next_cd_audio_buffer_callback, ciw); - else - bufnum = -1; - } - if (bufnum < 0) { - audio_cda_new_buffer(&ciw->cas, NULL, 0, -1, NULL, ciw); - } - uae_sem_post(&play_sem); -} - -static bool cdda_play2 (struct dev_info_ioctl *ciw, int *outpos) -{ - int cdda_pos = ciw->cdda_start; - int bufnum; - int buffered; - int i; - int oldplay; - int idleframes; - int muteframes; - int readblocksize = 2352 + 96; - int mode = currprefs.sound_cdaudio; - bool restart = false; - - while (ciw->cdda_play == 0) - sleep_millis(10); - oldplay = -1; - - ciw->cda_bufon[0] = ciw->cda_bufon[1] = 0; - bufnum = 0; - buffered = 0; - - memset(&ciw->cas, 0, sizeof(struct cd_audio_state)); - ciw->cda = new cda_audio (CDDA_BUFFERS, 2352, 44100, mode != 0); - - while (ciw->cdda_play > 0) { - - if (mode) { - while (ciw->cda_bufon[bufnum] && ciw->cdda_play > 0) { - if (cd_audio_mode_changed) { - restart = true; - goto end; - } - sleep_millis(10); - } - } else { - ciw->cda->wait(bufnum); - } - if (ciw->cdda_play <= 0) - goto end; - ciw->cda_bufon[bufnum] = 0; - - if (oldplay != ciw->cdda_play) { - idleframes = 0; - muteframes = 0; - bool seensub = false; - struct _timeb tb1, tb2; - _ftime (&tb1); - cdda_pos = ciw->cdda_start; - ciw->cd_last_pos = cdda_pos; - oldplay = ciw->cdda_play; - write_log (_T("IOCTL%s CDDA: playing from %d to %d\n"), - ciw->usesptiread ? _T("(SPTI)") : _T(""), ciw->cdda_start, ciw->cdda_end); - ciw->subcodevalid = false; - idleframes = ciw->cdda_delay_frames; - while (ciw->cdda_paused && ciw->cdda_play > 0) { - sleep_millis(10); - idleframes = -1; - } - // force spin up - if (isaudiotrack (&ciw->di.toc, cdda_pos)) - read_block (ciw, -1, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize); - if (!isaudiotrack (&ciw->di.toc, cdda_pos - 150)) - muteframes = 75; - - if (ciw->cdda_scan == 0) { - // find possible P-subchannel=1 and fudge starting point so that - // buggy CD32/CDTV software CD+G handling does not miss any frames - bool seenindex = false; - for (int sector = cdda_pos - 200; sector < cdda_pos; sector++) { - uae_u8 *dst = ciw->cda->buffers[bufnum]; - if (sector >= 0 && isaudiotrack (&ciw->di.toc, sector) && read_block (ciw, -1, dst, sector, 1, readblocksize)) { - uae_u8 subbuf[SUB_CHANNEL_SIZE]; - sub_deinterleave (dst + 2352, subbuf); - if (seenindex) { - for (int i = 2 * SUB_ENTRY_SIZE; i < SUB_CHANNEL_SIZE; i++) { - if (subbuf[i]) { // non-zero R-W subchannels? - int diff = cdda_pos - sector + 2; - write_log (_T("-> CD+G start pos fudge -> %d (%d)\n"), sector, -diff); - idleframes -= diff; - cdda_pos = sector; - seensub = true; - break; - } - } - } else if (subbuf[0] == 0xff) { // P == 1? - seenindex = true; - } - } - } - } - cdda_pos -= idleframes; - - if (*outpos < 0) { - _ftime (&tb2); - int diff = (tb2.time * (uae_s64)1000 + tb2.millitm) - (tb1.time * (uae_s64)1000 + tb1.millitm); - diff -= ciw->cdda_delay; - if (idleframes >= 0 && diff < 0 && ciw->cdda_play > 0) - sleep_millis(-diff); - if (diff > 0 && !seensub) { - int ch = diff / 7 + 25; - if (ch > idleframes) - ch = idleframes; - idleframes -= ch; - cdda_pos += ch; - } - setstate (ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos); - } - } - - if ((cdda_pos < ciw->cdda_end || ciw->cdda_end == 0xffffffff) && !ciw->cdda_paused && ciw->cdda_play) { - - if (idleframes <= 0 && cdda_pos >= ciw->cdda_start && !isaudiotrack (&ciw->di.toc, cdda_pos)) { - setstate (ciw, AUDIO_STATUS_PLAY_ERROR, -1); - write_log (_T("IOCTL: attempted to play data track %d\n"), cdda_pos); - goto end; // data track? - } - - gui_flicker_led (LED_CD, ciw->di.unitnum - 1, LED_CD_AUDIO); - - uae_sem_wait (&ciw->sub_sem); - - ciw->subcodevalid = false; - memset (ciw->subcode, 0, sizeof ciw->subcode); - memset (ciw->cda->buffers[bufnum], 0, CDDA_BUFFERS * readblocksize); - - if (cdda_pos >= 0) { - - setstate(ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos); - - if (read_block (ciw, -1, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize)) { - for (i = 0; i < CDDA_BUFFERS; i++) { - memcpy (ciw->subcode + i * SUB_CHANNEL_SIZE, ciw->cda->buffers[bufnum] + readblocksize * i + 2352, SUB_CHANNEL_SIZE); - } - for (i = 1; i < CDDA_BUFFERS; i++) { - memmove (ciw->cda->buffers[bufnum] + 2352 * i, ciw->cda->buffers[bufnum] + readblocksize * i, 2352); - } - ciw->subcodevalid = true; - } - } - - for (i = 0; i < CDDA_BUFFERS; i++) { - if (muteframes > 0) { - memset (ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); - muteframes--; - } - if (idleframes > 0) { - idleframes--; - memset (ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); - memset (ciw->subcode + i * SUB_CHANNEL_SIZE, 0, SUB_CHANNEL_SIZE); - } else if (cdda_pos < ciw->cdda_start && ciw->cdda_scan == 0) { - memset (ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); - } - } - if (idleframes > 0) - ciw->subcodevalid = false; - - if (ciw->cdda_subfunc) - ciw->cdda_subfunc (ciw->subcode, CDDA_BUFFERS); - - uae_sem_post (&ciw->sub_sem); - - if (ciw->subcodevalid) { - uae_sem_wait (&ciw->sub_sem2); - memcpy (ciw->subcodebuf, ciw->subcode + (CDDA_BUFFERS - 1) * SUB_CHANNEL_SIZE, SUB_CHANNEL_SIZE); - uae_sem_post (&ciw->sub_sem2); - } - - if (mode) { - if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) { - ciw->cda_bufon[bufnum] = 1; - ioctl_next_cd_audio_buffer_callback(1 - bufnum, ciw); - } - audio_cda_volume(&ciw->cas, ciw->cdda_volume[0], ciw->cdda_volume[1]); - ciw->cda_bufon[bufnum] = 1; - } else { - ciw->cda_bufon[bufnum] = 1; - ciw->cda->setvolume (ciw->cdda_volume[0], ciw->cdda_volume[1]); - if (!ciw->cda->play (bufnum)) { - setstate (ciw, AUDIO_STATUS_PLAY_ERROR, -1); - goto end; // data track? - } - } - - if (ciw->cdda_scan) { - cdda_pos += ciw->cdda_scan * CDDA_BUFFERS; - if (cdda_pos < 0) - cdda_pos = 0; - } else { - if (cdda_pos < 0 && cdda_pos + CDDA_BUFFERS >= 0) - cdda_pos = 0; - else - cdda_pos += CDDA_BUFFERS; - } - - if (idleframes <= 0) { - if (cdda_pos - CDDA_BUFFERS < ciw->cdda_end && cdda_pos >= ciw->cdda_end) { - cdda_pos = ciw->cdda_end; - setstate (ciw, AUDIO_STATUS_PLAY_COMPLETE, cdda_pos); - ciw->cdda_play_finished = 1; - ciw->cdda_play = -1; - } - ciw->cd_last_pos = cdda_pos; - } - } - - if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) { - while (ciw->cdda_paused && ciw->cdda_play == oldplay) - sleep_millis(10); - } - - if (cd_audio_mode_changed) { - restart = true; - goto end; - } - - bufnum = 1 - bufnum; - - } - -end: - *outpos = cdda_pos; - if (mode) { - ioctl_next_cd_audio_buffer_callback(-1, ciw); - if (restart) - audio_cda_new_buffer(&ciw->cas, NULL, -1, -1, NULL, ciw); - } else { - ciw->cda->wait (0); - ciw->cda->wait (1); - } - - ciw->subcodevalid = false; - cd_audio_mode_changed = false; - delete ciw->cda; - - write_log (_T("IOCTL CDDA: thread killed\n")); - return restart; -} - -static void *cdda_play (void *v) +static int read_block_cda(struct cda_play *cda, int unitnum, uae_u8 *data, int sector, int size, int sectorsize) { - int outpos = -1; - struct dev_info_ioctl *ciw = (struct dev_info_ioctl*)v; - cd_audio_mode_changed = false; - for (;;) { - if (!cdda_play2(ciw, &outpos)) { - break; - } - ciw->cdda_start = outpos; - if (ciw->cdda_start + 150 >= ciw->cdda_end) { - if (ciw->cdda_play >= 0) - setstate (ciw, AUDIO_STATUS_PLAY_COMPLETE, ciw->cdda_end + 1); - ciw->cdda_play = -1; - break; - } - ciw->cdda_play = 1; - } - ciw->cdda_play = 0; - return NULL; -} - -static void cdda_stop (struct dev_info_ioctl *ciw) -{ - if (ciw->cdda_play != 0) { - ciw->cdda_play = -1; - while (ciw->cdda_play) { - sleep_millis(10); - } - } - ciw->cdda_play_finished = 0; - ciw->cdda_paused = 0; - ciw->subcodevalid = 0; + return read_block(&ciw32[unitnum], unitnum, data, sector, size, sectorsize); } /* pause/unpause CD audio */ @@ -786,9 +440,9 @@ static int ioctl_command_pause (int unitnum, int paused) struct dev_info_ioctl *ciw = unitisopen (unitnum); if (!ciw) return -1; - int old = ciw->cdda_paused; - if ((paused && ciw->cdda_play) || !paused) - ciw->cdda_paused = paused; + int old = ciw->cda.cdda_paused; + if ((paused && ciw->cda.cdda_play) || !paused) + ciw->cda.cdda_paused = paused; return old; } @@ -800,7 +454,7 @@ static int ioctl_command_stop (int unitnum) if (!ciw) return 0; - cdda_stop (ciw); + ciw_cdda_stop(&ciw->cda); return 1; } @@ -810,9 +464,9 @@ static uae_u32 ioctl_command_volume (int unitnum, uae_u16 volume_left, uae_u16 v struct dev_info_ioctl *ciw = unitisopen (unitnum); if (!ciw) return -1; - uae_u32 old = (ciw->cdda_volume[1] << 16) | (ciw->cdda_volume[0] << 0); - ciw->cdda_volume[0] = volume_left; - ciw->cdda_volume[1] = volume_right; + uae_u32 old = (ciw->cda.cdda_volume[1] << 16) | (ciw->cda.cdda_volume[0] << 0); + ciw->cda.cdda_volume[0] = volume_left; + ciw->cda.cdda_volume[1] = volume_right; return old; } @@ -823,29 +477,31 @@ static int ioctl_command_play (int unitnum, int startlsn, int endlsn, int scan, if (!ciw) return 0; - ciw->cdda_play_finished = 0; - ciw->cdda_subfunc = subfunc; - ciw->cdda_statusfunc = statusfunc; - ciw->cdda_scan = scan > 0 ? 10 : (scan < 0 ? 10 : 0); - ciw->cdda_delay = setstate (ciw, -1, -1); - ciw->cdda_delay_frames = setstate (ciw, -2, -1); - setstate (ciw, AUDIO_STATUS_NOT_SUPPORTED, -1); + ciw->cda.di = &ciw->di; + ciw->cda.cdda_play_finished = 0; + ciw->cda.cdda_subfunc = subfunc; + ciw->cda.cdda_statusfunc = statusfunc; + ciw->cda.cdda_scan = scan > 0 ? 10 : (scan < 0 ? 10 : 0); + ciw->cda.cdda_delay = ciw_cdda_setstate(&ciw->cda, -1, -1); + ciw->cda.cdda_delay_frames = ciw_cdda_setstate(&ciw->cda, -2, -1); + ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_NOT_SUPPORTED, -1); + ciw->cda.read_block = read_block_cda; if (!open_createfile (ciw, 0)) { - setstate (ciw, AUDIO_STATUS_PLAY_ERROR, -1); + ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_PLAY_ERROR, -1); return 0; } if (!isaudiotrack (&ciw->di.toc, startlsn)) { - setstate (ciw, AUDIO_STATUS_PLAY_ERROR, -1); + ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_PLAY_ERROR, -1); return 0; } - if (!ciw->cdda_play) { - uae_start_thread (_T("ioctl_cdda_play"), cdda_play, ciw, NULL); + if (!ciw->cda.cdda_play) { + uae_start_thread (_T("ioctl_cdda_play"), ciw_cdda_play, &ciw->cda, NULL); } - ciw->cdda_start = startlsn; - ciw->cdda_end = endlsn; - ciw->cd_last_pos = ciw->cdda_start; - ciw->cdda_play++; + ciw->cda.cdda_start = startlsn; + ciw->cda.cdda_end = endlsn; + ciw->cda.cd_last_pos = ciw->cda.cdda_start; + ciw->cda.cdda_play++; return 1; } @@ -874,11 +530,11 @@ static int ioctl_command_qcode (int unitnum, uae_u8 *buf, int sector, bool all) p = buf; status = AUDIO_STATUS_NO_STATUS; - if (ciw->cdda_play) { + if (ciw->cda.cdda_play) { status = AUDIO_STATUS_IN_PROGRESS; - if (ciw->cdda_paused) + if (ciw->cda.cdda_paused) status = AUDIO_STATUS_PAUSED; - } else if (ciw->cdda_play_finished) { + } else if (ciw->cda.cdda_play_finished) { status = AUDIO_STATUS_PLAY_COMPLETE; } @@ -888,22 +544,22 @@ static int ioctl_command_qcode (int unitnum, uae_u8 *buf, int sector, bool all) p = buf + 4; if (sector < 0) - pos = ciw->cd_last_pos; + pos = ciw->cda.cd_last_pos; else pos = sector; if (!regenerate) { - if (sector < 0 && ciw->subcodevalid && ciw->cdda_play) { - uae_sem_wait (&ciw->sub_sem2); + if (sector < 0 && ciw->cda.subcodevalid && ciw->cda.cdda_play) { + uae_sem_wait (&ciw->cda.sub_sem2); uae_u8 subbuf[SUB_CHANNEL_SIZE]; - sub_deinterleave (ciw->subcodebuf, subbuf); + sub_deinterleave (ciw->cda.subcodebuf, subbuf); memcpy (p, subbuf + 12, 12); - uae_sem_post (&ciw->sub_sem2); + uae_sem_post (&ciw->cda.sub_sem2); valid = true; } if (!valid && sector >= 0) { DWORD len; - uae_sem_wait (&ciw->sub_sem); + uae_sem_wait (&ciw->cda.sub_sem); seterrormode (ciw); RAW_READ_INFO rri; rri.DiskOffset.QuadPart = 2048 * (pos + 0); @@ -917,7 +573,7 @@ static int ioctl_command_qcode (int unitnum, uae_u8 *buf, int sector, bool all) reseterrormode (ciw); uae_u8 subbuf[SUB_CHANNEL_SIZE]; sub_deinterleave (ciw->tempbuffer + 2352, subbuf); - uae_sem_post (&ciw->sub_sem); + uae_sem_post (&ciw->cda.sub_sem); memcpy (p, subbuf + 12, 12); valid = true; } @@ -966,7 +622,7 @@ static int ioctl_command_rawread (int unitnum, uae_u8 *data, int sector, int siz if (log_scsi) write_log (_T("IOCTL rawread unit=%d sector=%d blocksize=%d\n"), unitnum, sector, sectorsize); - cdda_stop (ciw); + ciw_cdda_stop(&ciw->cda); gui_flicker_led (LED_CD, unitnum, LED_CD_ACTIVE); if (sectorsize > 0) { if (sectorsize != 2336 && sectorsize != 2352 && sectorsize != 2048 && @@ -975,7 +631,7 @@ static int ioctl_command_rawread (int unitnum, uae_u8 *data, int sector, int siz while (size-- > 0) { if (!read_block (ciw, unitnum, data, sector, 1, sectorsize)) break; - ciw->cd_last_pos = sector; + ciw->cda.cd_last_pos = sector; data += sectorsize; ret += sectorsize; sector++; @@ -1009,7 +665,7 @@ static int ioctl_command_rawread (int unitnum, uae_u8 *data, int sector, int siz reseterrormode (ciw); return ret; } - ciw->cd_last_pos = sector; + ciw->cda.cd_last_pos = sector; if (subs == 0) { memcpy (data, p, blocksize); @@ -1050,7 +706,7 @@ static int ioctl_command_readwrite (int unitnum, int sector, int size, int write if (ciw->usesptiread) return ioctl_command_rawread (unitnum, data, sector, size, 2048, 0); - cdda_stop (ciw); + ciw_cdda_stop(&ciw->cda); DWORD dtotal; int cnt = 3; @@ -1059,7 +715,7 @@ static int ioctl_command_readwrite (int unitnum, int sector, int size, int write if (!open_createfile (ciw, 0)) return 0; - ciw->cd_last_pos = sector; + ciw->cda.cd_last_pos = sector; while (cnt-- > 0) { LARGE_INTEGER offset; gui_flicker_led (LED_CD, unitnum, LED_CD_ACTIVE); @@ -1137,7 +793,7 @@ static int fetch_geometry (struct dev_info_ioctl *ciw, int unitnum, struct devic if (!open_createfile (ciw, 0)) return 0; - uae_sem_wait (&ciw->sub_sem); + uae_sem_wait (&ciw->cda.sub_sem); seterrormode (ciw); while (cnt-- > 0) { if (!DeviceIoControl (ciw->h, IOCTL_CDROM_GET_DRIVE_GEOMETRY, NULL, 0, &geom, sizeof (geom), &len, NULL)) { @@ -1148,13 +804,13 @@ static int fetch_geometry (struct dev_info_ioctl *ciw, int unitnum, struct devic continue; } reseterrormode (ciw); - uae_sem_post (&ciw->sub_sem); + uae_sem_post (&ciw->cda.sub_sem); return 0; } break; } reseterrormode (ciw); - uae_sem_post (&ciw->sub_sem); + uae_sem_post (&ciw->cda.sub_sem); if (di) { di->cylinders = geom.Cylinders.LowPart; di->sectorspertrack = geom.SectorsPerTrack; @@ -1178,7 +834,7 @@ static int eject (int unitnum, bool eject) return 0; if (!unitisopen (unitnum)) return 0; - cdda_stop (ciw); + ciw_cdda_stop(&ciw->cda); if (!open_createfile (ciw, 0)) return 0; int ret = 0; @@ -1328,8 +984,8 @@ static void trim (TCHAR *s) static int sys_cddev_open (struct dev_info_ioctl *ciw, int unitnum) { - ciw->cdda_volume[0] = 0x7fff; - ciw->cdda_volume[1] = 0x7fff; + ciw->cda.cdda_volume[0] = 0x7fff; + ciw->cda.cdda_volume[1] = 0x7fff; /* buffer must be page aligned for device access */ ciw->tempbuffer = (uae_u8*)VirtualAlloc (NULL, IOCTL_DATA_BUFFER, MEM_COMMIT, PAGE_READWRITE); if (!ciw->tempbuffer) { @@ -1436,8 +1092,8 @@ static int sys_cddev_open (struct dev_info_ioctl *ciw, int unitnum) write_log (_T("Device blacklisted\n")); goto error2; } - uae_sem_init (&ciw->sub_sem, 0, 1); - uae_sem_init (&ciw->sub_sem2, 0, 1); + uae_sem_init (&ciw->cda.sub_sem, 0, 1); + uae_sem_init (&ciw->cda.sub_sem2, 0, 1); //ciw->usesptiread = true; ioctl_command_stop (unitnum); update_device_info (unitnum); @@ -1458,12 +1114,12 @@ static void sys_cddev_close (struct dev_info_ioctl *ciw, int unitnum) { if (ciw->open == false) return; - cdda_stop (ciw); + ciw_cdda_stop(&ciw->cda); close_createfile (ciw); VirtualFree (ciw->tempbuffer, 0, MEM_RELEASE); ciw->tempbuffer = NULL; - uae_sem_destroy (&ciw->sub_sem); - uae_sem_destroy (&ciw->sub_sem2); + uae_sem_destroy (&ciw->cda.sub_sem); + uae_sem_destroy (&ciw->cda.sub_sem2); ciw->open = false; write_log (_T("IOCTL: device '%s' closed\n"), ciw->devname, unitnum); } diff --git a/od-win32/blkdev_win32_spti.cpp b/od-win32/blkdev_win32_spti.cpp index 1dfc0d00..502fd94d 100644 --- a/od-win32/blkdev_win32_spti.cpp +++ b/od-win32/blkdev_win32_spti.cpp @@ -7,10 +7,6 @@ * */ - -#define WIN32_LEAN_AND_MEAN -#define _WIN32_WINNT 0x500 - #include "sysconfig.h" #include "sysdeps.h" #include "options.h" @@ -35,8 +31,10 @@ #include // Guid definition #include // Device guids #include // for SetupDiXxx functions. - #include +#include + +#include "cda_play.h" #define INQUIRY_SIZE 36 @@ -64,6 +62,9 @@ struct dev_info_spti { bool open; bool enabled; struct device_info di; + struct cda_play cda; + uae_u8 sense[32]; + int senselen; }; static uae_sem_t scgp_sem; @@ -112,9 +113,15 @@ static int doscsi (struct dev_info_spti *di, int unitnum, SCSI_PASS_THROUGH_DIRE *err = 0; if (log_scsi) { - write_log (_T("SCSI, H=%X:%d:%d:%d:%d: "), di->handle, di->bus, di->path, di->target, di->lun); + write_log (_T("SCSI, H=%X:%d:%d:%d:%d:\n"), di->handle, di->bus, di->path, di->target, di->lun); scsi_log_before (swb->spt.Cdb, swb->spt.CdbLength, swb->spt.DataIn == SCSI_IOCTL_DATA_OUT ? (uae_u8*)swb->spt.DataBuffer : NULL, swb->spt.DataTransferLength); + for (int i = 0; i < swb->spt.CdbLength; i++) { + if (i > 0) + write_log(_T(".")); + write_log(_T("%02x"), swb->spt.Cdb[i]); + } + write_log(_T("\n")); } gui_flicker_led (LED_CD, unitnum, 1); swb->spt.ScsiStatus = 0; @@ -152,13 +159,28 @@ static int doscsi (struct dev_info_spti *di, int unitnum, SCSI_PASS_THROUGH_DIRE #define MODE_SELECT_10 0x55 #define MODE_SENSE_10 0x5A -static int execscsicmd (struct dev_info_spti *di, int unitnum, uae_u8 *data, int len, uae_u8 *inbuf, int inlen, int timeout, int *errp) +static int execscsicmd (struct dev_info_spti *di, int unitnum, uae_u8 *data, int len, uae_u8 *inbuf, int inlen, int timeout, int *errp, int bypass) { SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb; DWORD status; int err = 0; int dolen; + if (!bypass) { + if (data[0] == 0x03 && di->senselen > 0) { + memcpy(inbuf, di->sense, di->senselen); + di->senselen = 0; + return di->senselen; + } + int r = blkdev_is_audio_command(data[0]); + if (r > 0) { + di->senselen = sizeof(di->sense); + return blkdev_execute_audio_command(unitnum, data, len, inbuf, inlen, di->sense, &di->senselen); + } else if (r < 0) { + ciw_cdda_stop(&di->cda); + } + } + uae_sem_wait (&scgp_sem); memset (&swb, 0, sizeof (swb)); swb.spt.Length = sizeof (SCSI_PASS_THROUGH); @@ -185,12 +207,64 @@ static int execscsicmd (struct dev_info_spti *di, int unitnum, uae_u8 *data, int return dolen; } +static int read_block_cda(struct cda_play *cda, int unitnum, uae_u8 *data, int sector, int size, int sectorsize) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return 0; + if (sectorsize != 2352 + 96) + return 0; + uae_u8 cmd[12] = { 0xbe, 0, (uae_u8)(sector >> 24), (uae_u8)(sector >> 16), (uae_u8)(sector >> 8), (uae_u8)(sector >> 0), (uae_u8)(size >> 16), (uae_u8)(size >> 8), (uae_u8)(size >> 0), 0x10, 1, 0 }; + int err = 0; + int len = execscsicmd(di, unitnum, cmd, sizeof(cmd), data, size * sectorsize, -1, &err, true); + if (len >= 0) + return len; + return -1; +} + static int execscsicmd_direct (int unitnum, struct amigascsi *as) { struct dev_info_spti *di = unitisopen (unitnum); if (!di) return -1; + if (as->cmd[0] == 0x03 && di->senselen > 0) { + memcpy(as->data, di->sense, di->senselen); + as->actual = di->senselen; + as->status = 0; + as->sactual = 0; + as->cmdactual = as->cmd_len; + di->senselen = 0; + return 0; + } + int r = blkdev_is_audio_command(as->cmd[0]); + if (r > 0) { + di->senselen = sizeof(di->sense); + int len = blkdev_execute_audio_command(unitnum, as->cmd, as->cmd_len, as->data, as->len, di->sense, &di->senselen); + as->actual = len; + as->cmdactual = as->cmd_len; + as->sactual = 0; + as->status = 0; + if (len < 0) { + /* copy sense? */ + if (as->sense_len > 32) + as->sense_len = 32; + int senselen = (as->flags & 4) ? 4 : /* SCSIF_OLDAUTOSENSE */ + (as->flags & 2) ? as->sense_len : /* SCSIF_AUTOSENSE */ 32; + if (senselen > 0) { + for (as->sactual = 0; as->sactual < di->senselen && as->sactual < senselen; as->sactual++) { + as->sensedata[as->sactual] = di->sense[as->sactual]; + } + } + as->actual = 0; + as->status = 2; + return 45; + } + return 0; + } else if (r < 0) { + ciw_cdda_stop(&di->cda); + } + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb; DWORD status; int sactual = 0, i; @@ -267,7 +341,7 @@ static uae_u8 *execscsicmd_out (int unitnum, uae_u8 *data, int len) struct dev_info_spti *di = unitisopen (unitnum); if (!di) return 0; - int v = execscsicmd (di, unitnum, data, len, 0, 0, -1, NULL); + int v = execscsicmd (di, unitnum, data, len, 0, 0, -1, NULL, false); if (v < 0) return 0; return data; @@ -278,7 +352,7 @@ static uae_u8 *execscsicmd_in (int unitnum, uae_u8 *data, int len, int *outlen) struct dev_info_spti *di = unitisopen (unitnum); if (!di) return 0; - int v = execscsicmd (di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE, -1, NULL); + int v = execscsicmd (di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE, -1, NULL, false); if (v < 0) return 0; if (v == 0) @@ -290,7 +364,7 @@ static uae_u8 *execscsicmd_in (int unitnum, uae_u8 *data, int len, int *outlen) static uae_u8 *execscsicmd_in_internal (struct dev_info_spti *di, int unitnum, uae_u8 *data, int len, int *outlen, int timeout) { - int v = execscsicmd (di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE, timeout, NULL); + int v = execscsicmd (di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE, timeout, NULL, false); if (v < 0) return 0; if (v == 0) @@ -304,8 +378,11 @@ static void close_scsi_device2 (struct dev_info_spti *di) { if (di->open == false) return; - if (di->handle != INVALID_HANDLE_VALUE) - CloseHandle (di->handle); + if (di->handle != INVALID_HANDLE_VALUE) { + CloseHandle(di->handle); + uae_sem_destroy(&di->cda.sub_sem); + uae_sem_destroy(&di->cda.sub_sem2); + } di->handle = INVALID_HANDLE_VALUE; di->open = false; } @@ -370,7 +447,7 @@ static int mediacheck (struct dev_info_spti *di, int unitnum) uae_u8 cmd [6] = { 0,0,0,0,0,0 }; /* TEST UNIT READY */ if (di->open == false) return -1; - int v = execscsicmd (di, unitnum, cmd, sizeof cmd, 0, 0, 0, NULL); + int v = execscsicmd (di, unitnum, cmd, sizeof cmd, 0, 0, 0, NULL, false); return v >= 0 ? 1 : 0; } @@ -403,6 +480,7 @@ static int mediacheck_full (struct dev_info_spti *di, int unitnum, struct device dinfo->write_protected = (p[3] & 0x80) ? 1 : 0; } } + sys_command_cd_toc(unitnum, &di->di.toc); // write_log (_T("mediacheck_full(%d,%d,%d,%d,%d)\n"), // di->bytespersector, di->sectorspertrack, di->trackspercylinder, di->cylinders, di->write_protected); return 1; @@ -514,7 +592,7 @@ static int open_scsi_device2 (struct dev_info_spti *di, int unitnum) int err = 0; uae_u8 inqdata[INQUIRY_SIZE + 1] = { 0 }; checkcapabilities (di); - execscsicmd(di, unitnum, inqdata, 6, NULL, 0, 0, &err); + execscsicmd(di, unitnum, inqdata, 6, NULL, 0, 0, &err, false); if (err) { write_log(_T("SPTI: TUR failed unit %d, err=%d ('%s':%d:%d:%d:%d)\n"), unitnum, err, dev, di->bus, di->path, di->target, di->lun); @@ -556,6 +634,11 @@ static int open_scsi_device2 (struct dev_info_spti *di, int unitnum) update_device_info (unitnum); if (di->type == INQ_ROMD) blkdev_cd_change (unitnum, di->drvletter ? di->drvlettername : di->name); + di->cda.cdda_volume[0] = 0x7fff; + di->cda.cdda_volume[1] = 0x7fff; + uae_sem_init(&di->cda.sub_sem, 0, 1); + uae_sem_init(&di->cda.sub_sem2, 0, 1); + di->cda.cd_last_pos = 150; return 1; } xfree (dev); @@ -893,6 +976,138 @@ static int rescan (void) } +/* pause/unpause CD audio */ +static int ioctl_command_pause(int unitnum, int paused) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return -1; + int old = di->cda.cdda_paused; + if ((paused && di->cda.cdda_play) || !paused) + di->cda.cdda_paused = paused; + return old; +} + + +/* stop CD audio */ +static int ioctl_command_stop(int unitnum) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return 0; + + ciw_cdda_stop(&di->cda); + + return 1; +} + +static uae_u32 ioctl_command_volume(int unitnum, uae_u16 volume_left, uae_u16 volume_right) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return -1; + uae_u32 old = (di->cda.cdda_volume[1] << 16) | (di->cda.cdda_volume[0] << 0); + di->cda.cdda_volume[0] = volume_left; + di->cda.cdda_volume[1] = volume_right; + return old; +} + +/* play CD audio */ +static int ioctl_command_play(int unitnum, int startlsn, int endlsn, int scan, play_status_callback statusfunc, play_subchannel_callback subfunc) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return 0; + + di->cda.di = &di->di; + di->cda.subcodevalid = false; + di->cda.cdda_play_finished = 0; + di->cda.cdda_subfunc = subfunc; + di->cda.cdda_statusfunc = statusfunc; + di->cda.cdda_scan = scan > 0 ? 10 : (scan < 0 ? 10 : 0); + di->cda.cdda_delay = ciw_cdda_setstate(&di->cda, -1, -1); + di->cda.cdda_delay_frames = ciw_cdda_setstate(&di->cda, -2, -1); + ciw_cdda_setstate(&di->cda, AUDIO_STATUS_NOT_SUPPORTED, -1); + di->cda.read_block = read_block_cda; + + if (!isaudiotrack(&di->di.toc, startlsn)) { + ciw_cdda_setstate(&di->cda, AUDIO_STATUS_PLAY_ERROR, -1); + return 0; + } + if (!di->cda.cdda_play) { + uae_start_thread(_T("ioctl_cdda_play"), ciw_cdda_play, &di->cda, NULL); + } + di->cda.cdda_start = startlsn; + di->cda.cdda_end = endlsn; + di->cda.cd_last_pos = di->cda.cdda_start; + di->cda.cdda_play++; + + return 1; +} + +static void sub_deinterleave(const uae_u8 *s, uae_u8 *d) +{ + for (int i = 0; i < 8 * 12; i++) { + int dmask = 0x80; + int smask = 1 << (7 - (i / 12)); + (*d) = 0; + for (int j = 0; j < 8; j++) { + (*d) |= (s[(i % 12) * 8 + j] & smask) ? dmask : 0; + dmask >>= 1; + } + d++; + } +} + +/* read qcode */ +static int ioctl_command_qcode(int unitnum, uae_u8 *buf, int sector, bool all) +{ + struct dev_info_spti *di = unitisopen(unitnum); + if (!di) + return 0; + struct cda_play *cd = &di->cda; + + if (all) + return 0; + + memset(buf, 0, SUBQ_SIZE); + uae_u8 *p = buf; + + int status = AUDIO_STATUS_NO_STATUS; + if (di->cda.cdda_play) { + status = AUDIO_STATUS_IN_PROGRESS; + if (di->cda.cdda_paused) + status = AUDIO_STATUS_PAUSED; + } else if (di->cda.cdda_play_finished) { + status = AUDIO_STATUS_PLAY_COMPLETE; + } + + p[1] = status; + p[3] = 12; + + p = buf + 4; + + if (cd->subcodevalid) { + uae_sem_wait(&di->cda.sub_sem2); + uae_u8 subbuf[SUB_CHANNEL_SIZE]; + sub_deinterleave(di->cda.subcodebuf, subbuf); + memcpy(p, subbuf + 12, 12); + uae_sem_post(&di->cda.sub_sem2); + } else { + int pos = di->cda.cd_last_pos; + int trk = cdtracknumber(&di->di.toc, pos); + struct cd_toc *t = &di->di.toc.toc[trk]; + p[0] = (t->control << 4) | (t->adr << 0); + p[1] = tobcd(trk); + p[2] = tobcd(1); + uae_u32 msf = lsn2msf(pos); + tolongbcd(p + 7, msf); + msf = lsn2msf(pos - t->paddress - 150); + tolongbcd(p + 3, msf); + } + + return 1; +} #endif @@ -900,5 +1115,6 @@ struct device_functions devicefunc_scsi_spti = { _T("SPTI"), open_scsi_bus, close_scsi_bus, open_scsi_device, close_scsi_device, info_device, execscsicmd_out, execscsicmd_in, execscsicmd_direct, - 0, 0, 0, 0, 0, 0, 0, 0, 0, check_isatapi, 0, 0 + ioctl_command_pause, ioctl_command_stop, ioctl_command_play, ioctl_command_volume, ioctl_command_qcode, + 0, 0, 0, 0, check_isatapi, 0, 0 }; diff --git a/od-win32/cda_play.cpp b/od-win32/cda_play.cpp index 35c63099..c7324357 100644 --- a/od-win32/cda_play.cpp +++ b/od-win32/cda_play.cpp @@ -16,11 +16,15 @@ #include "options.h" #include "dxwrap.h" #include "audio.h" +#include "blkdev.h" +#include "threaddep/thread.h" #include #include +#include #include "win32.h" +#include "gui.h" #include "cda_play.h" @@ -254,3 +258,368 @@ bool cda_audio::isplaying(int bufnum) return false; return (whdr[bufnum].dwFlags & WHDR_DONE) == 0; } + + +static uae_sem_t play_sem; + +static void sub_deinterleave(const uae_u8 *s, uae_u8 *d) +{ + for (int i = 0; i < 8 * 12; i++) { + int dmask = 0x80; + int smask = 1 << (7 - (i / 12)); + (*d) = 0; + for (int j = 0; j < 8; j++) { + (*d) |= (s[(i % 12) * 8 + j] & smask) ? dmask : 0; + dmask >>= 1; + } + d++; + } +} + +static void sub_to_deinterleaved(const uae_u8 *s, uae_u8 *d) +{ + for (int i = 0; i < 8 * 12; i++) { + int dmask = 0x80; + int smask = 1 << (7 - (i / 12)); + (*d) = 0; + for (int j = 0; j < 8; j++) { + (*d) |= (s[(i % 12) * 8 + j] & smask) ? dmask : 0; + dmask >>= 1; + } + d++; + } +} + +static void cdda_closewav(struct cda_play *ciw) +{ + if (ciw->cdda_wavehandle != NULL) + waveOutClose(ciw->cdda_wavehandle); + ciw->cdda_wavehandle = NULL; +} + +// DAE CDDA based on Larry Osterman's "Playing Audio CDs" blog series + +static int cdda_openwav(struct cda_play *ciw) +{ + WAVEFORMATEX wav = { 0 }; + MMRESULT mmr; + + wav.cbSize = 0; + wav.nChannels = 2; + wav.nSamplesPerSec = 44100; + wav.wBitsPerSample = 16; + wav.nBlockAlign = wav.wBitsPerSample / 8 * wav.nChannels; + wav.nAvgBytesPerSec = wav.nBlockAlign * wav.nSamplesPerSec; + wav.wFormatTag = WAVE_FORMAT_PCM; + mmr = waveOutOpen(&ciw->cdda_wavehandle, WAVE_MAPPER, &wav, 0, 0, WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT); + if (mmr != MMSYSERR_NOERROR) { + write_log(_T("IOCTL CDDA: wave open %d\n"), mmr); + cdda_closewav(ciw); + return 0; + } + return 1; +} + +int ciw_cdda_setstate(struct cda_play *ciw, int state, int playpos) +{ + ciw->cdda_play_state = state; + if (ciw->cdda_statusfunc) + return ciw->cdda_statusfunc(ciw->cdda_play_state, playpos); + return 0; +} + +void ioctl_next_cd_audio_buffer_callback(int bufnum, void *param) +{ + struct cda_play *ciw = (struct cda_play*)param; + uae_sem_wait(&play_sem); + if (bufnum >= 0) { + ciw->cda_bufon[bufnum] = 0; + bufnum = 1 - bufnum; + if (ciw->cda_bufon[bufnum]) + audio_cda_new_buffer(&ciw->cas, (uae_s16 *)ciw->cda->buffers[bufnum], CDDA_BUFFERS * 2352 / 4, bufnum, ioctl_next_cd_audio_buffer_callback, ciw); + else + bufnum = -1; + } + if (bufnum < 0) { + audio_cda_new_buffer(&ciw->cas, NULL, 0, -1, NULL, ciw); + } + uae_sem_post(&play_sem); +} + +static bool cdda_play2(struct cda_play *ciw, int *outpos) +{ + int cdda_pos = ciw->cdda_start; + int bufnum; + int buffered; + int i; + int oldplay; + int idleframes; + int muteframes; + int readblocksize = 2352 + 96; + int mode = currprefs.sound_cdaudio; + bool restart = false; + + while (ciw->cdda_play == 0) + sleep_millis(10); + oldplay = -1; + + ciw->cda_bufon[0] = ciw->cda_bufon[1] = 0; + bufnum = 0; + buffered = 0; + + memset(&ciw->cas, 0, sizeof(struct cd_audio_state)); + ciw->cda = new cda_audio(CDDA_BUFFERS, 2352, 44100, mode != 0); + + while (ciw->cdda_play > 0) { + + if (mode) { + while (ciw->cda_bufon[bufnum] && ciw->cdda_play > 0) { + if (cd_audio_mode_changed) { + restart = true; + goto end; + } + sleep_millis(10); + } + } else { + ciw->cda->wait(bufnum); + } + if (ciw->cdda_play <= 0) + goto end; + ciw->cda_bufon[bufnum] = 0; + + if (oldplay != ciw->cdda_play) { + idleframes = 0; + muteframes = 0; + bool seensub = false; + struct _timeb tb1, tb2; + _ftime(&tb1); + cdda_pos = ciw->cdda_start; + ciw->cd_last_pos = cdda_pos; + oldplay = ciw->cdda_play; + write_log(_T("CDDA: playing from %d to %d\n"), ciw->cdda_start, ciw->cdda_end); + ciw->subcodevalid = false; + idleframes = ciw->cdda_delay_frames; + while (ciw->cdda_paused && ciw->cdda_play > 0) { + sleep_millis(10); + idleframes = -1; + } + // force spin up + if (isaudiotrack(&ciw->di->toc, cdda_pos)) + ciw->read_block(ciw, ciw->unitnum, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize); + if (!isaudiotrack(&ciw->di->toc, cdda_pos - 150)) + muteframes = 75; + + if (ciw->cdda_scan == 0) { + // find possible P-subchannel=1 and fudge starting point so that + // buggy CD32/CDTV software CD+G handling does not miss any frames + bool seenindex = false; + for (int sector = cdda_pos - 200; sector < cdda_pos; sector++) { + uae_u8 *dst = ciw->cda->buffers[bufnum]; + if (sector >= 0 && isaudiotrack(&ciw->di->toc, sector) && ciw->read_block(ciw, ciw->unitnum, dst, sector, 1, readblocksize) > 0) { + uae_u8 subbuf[SUB_CHANNEL_SIZE]; + sub_deinterleave(dst + 2352, subbuf); + if (seenindex) { + for (int i = 2 * SUB_ENTRY_SIZE; i < SUB_CHANNEL_SIZE; i++) { + if (subbuf[i]) { // non-zero R-W subchannels? + int diff = cdda_pos - sector + 2; + write_log(_T("-> CD+G start pos fudge -> %d (%d)\n"), sector, -diff); + idleframes -= diff; + cdda_pos = sector; + seensub = true; + break; + } + } + } else if (subbuf[0] == 0xff) { // P == 1? + seenindex = true; + } + } + } + } + cdda_pos -= idleframes; + + if (*outpos < 0) { + _ftime(&tb2); + int diff = (tb2.time * (uae_s64)1000 + tb2.millitm) - (tb1.time * (uae_s64)1000 + tb1.millitm); + diff -= ciw->cdda_delay; + if (idleframes >= 0 && diff < 0 && ciw->cdda_play > 0) + sleep_millis(-diff); + if (diff > 0 && !seensub) { + int ch = diff / 7 + 25; + if (ch > idleframes) + ch = idleframes; + idleframes -= ch; + cdda_pos += ch; + } + ciw_cdda_setstate(ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos); + } + } + + if ((cdda_pos < ciw->cdda_end || ciw->cdda_end == 0xffffffff) && !ciw->cdda_paused && ciw->cdda_play) { + + if (idleframes <= 0 && cdda_pos >= ciw->cdda_start && !isaudiotrack(&ciw->di->toc, cdda_pos)) { + ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_ERROR, -1); + write_log(_T("IOCTL: attempted to play data track %d\n"), cdda_pos); + goto end; // data track? + } + + gui_flicker_led(LED_CD, ciw->unitnum - 1, LED_CD_AUDIO); + + uae_sem_wait(&ciw->sub_sem); + + ciw->subcodevalid = false; + memset(ciw->subcode, 0, sizeof ciw->subcode); + memset(ciw->cda->buffers[bufnum], 0, CDDA_BUFFERS * readblocksize); + + if (cdda_pos >= 0) { + + ciw_cdda_setstate(ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos); + + int r = ciw->read_block(ciw, ciw->unitnum, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize); + if (r < 0) { + ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_ERROR, -1); + uae_sem_post(&ciw->sub_sem); + goto end; + } + if (r > 0) { + for (i = 0; i < CDDA_BUFFERS; i++) { + memcpy(ciw->subcode + i * SUB_CHANNEL_SIZE, ciw->cda->buffers[bufnum] + readblocksize * i + 2352, SUB_CHANNEL_SIZE); + } + for (i = 1; i < CDDA_BUFFERS; i++) { + memmove(ciw->cda->buffers[bufnum] + 2352 * i, ciw->cda->buffers[bufnum] + readblocksize * i, 2352); + } + ciw->subcodevalid = true; + } + } + + for (i = 0; i < CDDA_BUFFERS; i++) { + if (muteframes > 0) { + memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); + muteframes--; + } + if (idleframes > 0) { + idleframes--; + memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); + memset(ciw->subcode + i * SUB_CHANNEL_SIZE, 0, SUB_CHANNEL_SIZE); + } else if (cdda_pos < ciw->cdda_start && ciw->cdda_scan == 0) { + memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352); + } + } + if (idleframes > 0) + ciw->subcodevalid = false; + + if (ciw->cdda_subfunc) + ciw->cdda_subfunc(ciw->subcode, CDDA_BUFFERS); + + uae_sem_post(&ciw->sub_sem); + + if (ciw->subcodevalid) { + uae_sem_wait(&ciw->sub_sem2); + memcpy(ciw->subcodebuf, ciw->subcode + (CDDA_BUFFERS - 1) * SUB_CHANNEL_SIZE, SUB_CHANNEL_SIZE); + uae_sem_post(&ciw->sub_sem2); + } + + if (mode) { + if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) { + ciw->cda_bufon[bufnum] = 1; + ioctl_next_cd_audio_buffer_callback(1 - bufnum, ciw); + } + audio_cda_volume(&ciw->cas, ciw->cdda_volume[0], ciw->cdda_volume[1]); + ciw->cda_bufon[bufnum] = 1; + } else { + ciw->cda_bufon[bufnum] = 1; + ciw->cda->setvolume(ciw->cdda_volume[0], ciw->cdda_volume[1]); + if (!ciw->cda->play(bufnum)) { + ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_ERROR, -1); + goto end; // data track? + } + } + + if (ciw->cdda_scan) { + cdda_pos += ciw->cdda_scan * CDDA_BUFFERS; + if (cdda_pos < 0) + cdda_pos = 0; + } else { + if (cdda_pos < 0 && cdda_pos + CDDA_BUFFERS >= 0) + cdda_pos = 0; + else + cdda_pos += CDDA_BUFFERS; + } + + if (idleframes <= 0) { + if (cdda_pos - CDDA_BUFFERS < ciw->cdda_end && cdda_pos >= ciw->cdda_end) { + cdda_pos = ciw->cdda_end; + ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_COMPLETE, cdda_pos); + ciw->cdda_play_finished = 1; + ciw->cdda_play = -1; + } + ciw->cd_last_pos = cdda_pos; + } + } + + if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) { + while (ciw->cdda_paused && ciw->cdda_play == oldplay) + sleep_millis(10); + } + + if (cd_audio_mode_changed) { + restart = true; + goto end; + } + + bufnum = 1 - bufnum; + + } + +end: + *outpos = cdda_pos; + if (mode) { + ioctl_next_cd_audio_buffer_callback(-1, ciw); + if (restart) + audio_cda_new_buffer(&ciw->cas, NULL, -1, -1, NULL, ciw); + } else { + ciw->cda->wait(0); + ciw->cda->wait(1); + } + + ciw->subcodevalid = false; + cd_audio_mode_changed = false; + delete ciw->cda; + + write_log(_T("CDDA: thread killed\n")); + return restart; +} + +void *ciw_cdda_play(void *v) +{ + struct cda_play *ciw = (struct cda_play *)v; + int outpos = -1; + cd_audio_mode_changed = false; + for (;;) { + if (!cdda_play2(ciw, &outpos)) { + break; + } + ciw->cdda_start = outpos; + if (ciw->cdda_start + 150 >= ciw->cdda_end) { + if (ciw->cdda_play >= 0) + ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_COMPLETE, ciw->cdda_end + 1); + ciw->cdda_play = -1; + break; + } + ciw->cdda_play = 1; + } + ciw->cdda_play = 0; + return NULL; +} + +void ciw_cdda_stop(struct cda_play *ciw) +{ + if (ciw->cdda_play != 0) { + ciw->cdda_play = -1; + while (ciw->cdda_play) { + sleep_millis(10); + } + } + ciw->cdda_play_finished = 0; + ciw->cdda_paused = 0; + ciw->subcodevalid = 0; +} diff --git a/od-win32/cda_play.h b/od-win32/cda_play.h index d36d2a78..d290fc8c 100644 --- a/od-win32/cda_play.h +++ b/od-win32/cda_play.h @@ -3,6 +3,9 @@ #include #endif +#include "audio.h" +#include "blkdev.h" + extern volatile bool cd_audio_mode_changed; class cda_audio @@ -35,3 +38,36 @@ public: bool isplaying(int bufnum); }; +#define CDDA_BUFFERS 14 + +typedef int (*cda_play_read_block)(struct cda_play *, int unitnum, uae_u8 *data, int sector, int size, int sectorsize); + +struct cda_play +{ + int unitnum; + int cdda_volume[2]; + HWAVEOUT cdda_wavehandle; + int cd_last_pos; + int cdda_play; + int cdda_play_finished; + int cdda_scan; + int cdda_paused; + int cdda_start, cdda_end; + play_subchannel_callback cdda_subfunc; + play_status_callback cdda_statusfunc; + int cdda_play_state; + int cdda_delay, cdda_delay_frames; + cda_audio *cda; + volatile int cda_bufon[2]; + struct cd_audio_state cas; + bool subcodevalid; + uae_sem_t sub_sem, sub_sem2; + uae_u8 subcode[SUB_CHANNEL_SIZE * CDDA_BUFFERS]; + uae_u8 subcodebuf[SUB_CHANNEL_SIZE]; + struct device_info *di; + cda_play_read_block read_block; +}; + +void *ciw_cdda_play(void *ciw); +void ciw_cdda_stop(struct cda_play *ciw); +int ciw_cdda_setstate(struct cda_play *ciw, int state, int playpos); \ No newline at end of file -- 2.47.3