mirror of
https://github.com/LIV2/WinUAE.git
synced 2025-12-06 00:12:52 +00:00
410 lines
10 KiB
C++
410 lines
10 KiB
C++
|
|
/*
|
|
* UAE
|
|
*
|
|
* Win32 audio player for CDA emulation
|
|
*
|
|
* Copyright 2010 Toni Wilen
|
|
*
|
|
*/
|
|
|
|
#define CDADS 0
|
|
|
|
#include "sysconfig.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include "options.h"
|
|
#include "render.h"
|
|
#include "audio.h"
|
|
#include "blkdev.h"
|
|
#include "threaddep/thread.h"
|
|
|
|
#include <dsound.h>
|
|
#include <mmreg.h>
|
|
#include <sys/timeb.h>
|
|
|
|
#include "win32.h"
|
|
#include "gui.h"
|
|
|
|
#include "cda_play.h"
|
|
|
|
cda_audio::~cda_audio()
|
|
{
|
|
for (int i = 0; i < 2; i++) {
|
|
xfree (buffers[i]);
|
|
buffers[i] = NULL;
|
|
}
|
|
}
|
|
|
|
cda_audio::cda_audio(int num_sectors, int sectorsize, int samplerate)
|
|
{
|
|
active = false;
|
|
playing = false;
|
|
volume[0] = volume[1] = 0;
|
|
|
|
bufsize = num_sectors * sectorsize;
|
|
this->sectorsize = sectorsize;
|
|
for (int i = 0; i < 2; i++) {
|
|
buffers[i] = xcalloc (uae_u8, num_sectors * ((bufsize + 4095) & ~4095));
|
|
}
|
|
this->num_sectors = num_sectors;
|
|
|
|
}
|
|
|
|
void cda_audio::setvolume(int left, int right)
|
|
{
|
|
for (int j = 0; j < 2; j++) {
|
|
volume[j] = j == 0 ? left : right;
|
|
volume[j] = sound_cd_volume[j] * volume[j] / 32768;
|
|
if (volume[j])
|
|
volume[j]++;
|
|
volume[j] = volume[j] * (100 - currprefs.sound_volume_master) / 100;
|
|
if (volume[j] >= 32768)
|
|
volume[j] = 32768;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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);
|
|
|
|
while (ciw->cdda_play > 0) {
|
|
|
|
while (ciw->cda_bufon[bufnum] && ciw->cdda_play > 0) {
|
|
if (cd_audio_mode_changed) {
|
|
restart = true;
|
|
goto end;
|
|
}
|
|
sleep_millis(10);
|
|
}
|
|
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 = (int)((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 (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;
|
|
|
|
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;
|
|
ioctl_next_cd_audio_buffer_callback(-1, ciw);
|
|
if (restart)
|
|
audio_cda_new_buffer(&ciw->cas, NULL, -1, -1, NULL, ciw);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|