1
0
Fork 0

Refactor CD-DA flow by removing intermediate buffers and loops

Thanks to @ripsaw8080 for insight into CD-DA channel mapping,
@hail-to-the-ryzen for testing and flagging a position-tracking bug,
and @dreamer_ for guidance and code review.

The CD-DA volume and channel mapping loops were moved to generic mixer
calls and no longer require a pre-processing loop:
 - Application-controlled CD-DA volume adjustment is now applied using
   an existing mixer volume scalar that was previously unused by the
   CD-DA code.
 - Mapping of CD-DA left and right channels is now applied at the tail
   end of the mixer's sample ingest sequence.

The following have been removed:
 - The CD-DA callback chunk-wise circular buffer
 - The decode buffers in the Opus and MP3 decoders
 - The decode buffer and conversion buffers in SDL_Sound
These removals and API changes allow the image player's buffer
to be passed-through ultimately to the audio codec, skipping multiple
intermediate buffers.
This commit is contained in:
krcroft 2019-11-11 13:36:31 -08:00 committed by Patryk Obara
parent 5a9dd2866b
commit d1a6f373cb
5 changed files with 310 additions and 336 deletions

View file

@ -46,8 +46,11 @@ extern Bit8u MixTemp[MIXER_BUFSIZE];
class MixerChannel {
public:
MixerChannel(MIXER_Handler _handler, Bitu _freq, const char * _name);
void SetVolume(float _left,float _right);
void SetScale( float f );
void SetScale(float f);
void SetScale(float _left, float _right);
void MapChannels(Bit8u _left, Bit8u _right);
void UpdateVolume(void);
void SetFreq(Bitu _freq);
void Mix(Bitu _needed);
@ -77,24 +80,25 @@ public:
void FillUp(void);
void Enable(bool _yesno);
MIXER_Handler handler;
float volmain[2];
float scale;
Bit32s volmul[2];
//This gets added the frequency counter each mixer step
Bitu freq_add;
//When this flows over a new sample needs to be read from the device
Bitu freq_counter;
//Timing on how many samples have been done and were needed by th emixer
Bitu done, needed;
//Previous and next samples
Bits prevSample[2];
Bits nextSample[2];
const char * name;
bool interpolate;
bool enabled;
MixerChannel * next;
float volmain[2];
MixerChannel* next;
const char* name;
Bitu done; //Timing on how many samples have been done by the mixer
bool enabled;
private:
MixerChannel();
MIXER_Handler handler;
Bitu freq_add; //This gets added the frequency counter each mixer step
Bitu freq_counter; //When this flows over a new sample needs to be read from the device
Bitu needed; //Timing on how many samples were needed by the mixer
Bits prev_sample[2]; //Previous and next samples
Bits next_sample[2];
Bit32s volmul[2];
float scale[2];
Bit8u channel_map[2]; //Output channel mapping
bool interpolate;
};
MixerChannel * MIXER_AddChannel(MIXER_Handler handler,Bitu freq,const char * name);

View file

@ -36,13 +36,11 @@
#include "mixer.h"
#include "../libs/decoders/SDL_sound.h"
#define RAW_SECTOR_SIZE 2352
#define COOKED_SECTOR_SIZE 2048
#define AUDIO_DECODE_BUFFER_SIZE 16512
// 16512 is 16384 + 128, which enough for four 4KB decode audio chunks plus 128 bytes extra,
// which accomodate the leftovers from typically callbacks, which also helps minimize
// most of the time. This size is also an even multiple of 8-bytes, allowing the compiler to
// use "large-D" SIMD instructions on it.
// CDROM data and audio format constants
#define RAW_SECTOR_SIZE 2352
#define COOKED_SECTOR_SIZE 2048
#define BYTES_PER_TRACK_FRAME 4
#define REDBOOK_FRAMES_PER_SECOND 75
enum { CDROM_USE_SDL, CDROM_USE_ASPI, CDROM_USE_IOCTL_DIO, CDROM_USE_IOCTL_DX, CDROM_USE_IOCTL_MCI };
@ -53,17 +51,16 @@ typedef struct SMSF {
} TMSF;
typedef struct SCtrl {
Bit8u out[4]; // output channel
Bit8u vol[4]; // channel volume
Bit8u out[4]; // output channel mapping
Bit8u vol[4]; // channel volume (0 to 255)
} TCtrl;
// Conversion function from frames to Minutes/Second/Frames
//
template<typename T>
inline void frames_to_msf(int frames, T *m, T *s, T *f) {
const int cd_fps = 75;
*f = frames % cd_fps;
frames /= cd_fps;
*f = frames % REDBOOK_FRAMES_PER_SECOND;
frames /= REDBOOK_FRAMES_PER_SECOND;
*s = frames % 60;
frames /= 60;
*m = frames;
@ -72,8 +69,7 @@ inline void frames_to_msf(int frames, T *m, T *s, T *f) {
// Conversion function from Minutes/Second/Frames to frames
//
inline int msf_to_frames(int m, int s, int f) {
const int cd_fps = 75;
return m * 60 * cd_fps + s * cd_fps + f;
return m * 60 * REDBOOK_FRAMES_PER_SECOND + s * REDBOOK_FRAMES_PER_SECOND + f;
}
extern int CDROM_GetMountType(char* path, int force);
@ -161,7 +157,7 @@ private:
public:
virtual bool read(Bit8u *buffer, int seek, int count) = 0;
virtual bool seek(Bit32u offset) = 0;
virtual Bit16u decode(Bit8u *buffer) = 0;
virtual Bit32u decode(Bit16s *buffer, Bit32u desired_track_frames) = 0;
virtual Bit16u getEndian() = 0;
virtual Bit32u getRate() = 0;
virtual Bit8u getChannels() = 0;
@ -175,7 +171,7 @@ private:
BinaryFile (const char *filename, bool &error);
bool read(Bit8u *buffer, int seek, int count);
bool seek(Bit32u offset);
Bit16u decode(Bit8u *buffer);
Bit32u decode(Bit16s *buffer, Bit32u desired_track_frames);
Bit16u getEndian();
Bit32u getRate() { return 44100; }
Bit8u getChannels() { return 2; }
@ -191,7 +187,7 @@ private:
AudioFile (const char *filename, bool &error);
bool read(Bit8u *buffer, int seek, int count) { return false; }
bool seek(Bit32u offset);
Bit16u decode(Bit8u *buffer);
Bit32u decode(Bit16s *buffer, Bit32u desired_track_frames);
Bit16u getEndian();
Bit32u getRate();
Bit8u getChannels();
@ -237,26 +233,21 @@ static CDROM_Interface_Image* images[26];
private:
// player
static void CDAudioCallBack(Bitu len);
static void CDAudioCallBack(Bitu desired_frames);
int GetTrack(int sector);
static struct imagePlayer {
Bit8u buffer[AUDIO_DECODE_BUFFER_SIZE];
TCtrl ctrlData;
Bit16s buffer[MIXER_BUFSIZE * 2]; // 2 channels (max)
TrackFile *trackFile;
MixerChannel *channel;
CDROM_Interface_Image *cd;
void (MixerChannel::*addSamples) (Bitu, const Bit16s*);
Bit32u startFrame;
Bit32u currFrame;
Bit32u numFrames;
Bit32u playbackTotal;
Bit32s playbackRemaining;
Bit16u bufferPos;
Bit16u bufferConsumed;
void (MixerChannel::*addFrames) (Bitu, const Bit16s*);
Bit32u startRedbookFrame;
Bit32u totalRedbookFrames;
Bit32u playedTrackFrames;
Bit32u totalTrackFrames;
bool isPlaying;
bool isPaused;
bool ctrlUsed;
} player;
void ClearTracks();
@ -407,7 +398,7 @@ private:
SDL_mutex *mutex;
Bit8u buffer[8192];
int bufLen;
int currFrame;
int currFrame;
int targetFrame;
bool isPlaying;
bool isPaused;

View file

@ -27,14 +27,9 @@
#include <fstream>
#include <iostream>
#include <limits>
#include <limits.h> //GCC 2.95
#include <sstream>
#include <vector>
#include <sys/stat.h>
#include "cdrom.h"
#include "drives.h"
#include "support.h"
#include "setup.h"
#if !defined(WIN32)
#include <libgen.h>
@ -42,6 +37,11 @@
#include <string.h>
#endif
#include "cdrom.h"
#include "drives.h"
#include "support.h"
#include "setup.h"
using namespace std;
#define MAX_LINE_LENGTH 512
@ -96,12 +96,8 @@ int CDROM_Interface_Image::BinaryFile::getLength()
Bit16u CDROM_Interface_Image::BinaryFile::getEndian()
{
// Image files are read into native-endian byte-order
#if defined(WORDS_BIGENDIAN)
return AUDIO_S16MSB;
#else
// Image files are always little endian
return AUDIO_S16LSB;
#endif
}
@ -111,10 +107,10 @@ bool CDROM_Interface_Image::BinaryFile::seek(Bit32u offset)
return !file->fail();
}
Bit16u CDROM_Interface_Image::BinaryFile::decode(Bit8u *buffer)
Bit32u CDROM_Interface_Image::BinaryFile::decode(Bit16s *buffer, Bit32u desired_track_frames)
{
file->read((char*)buffer, chunkSize);
return (Bit16u) file->gcount();
file->read((char*)buffer, desired_track_frames * BYTES_PER_TRACK_FRAME);
return (Bit32u) file->gcount() / BYTES_PER_TRACK_FRAME;
}
CDROM_Interface_Image::AudioFile::AudioFile(const char *filename, bool &error)
@ -122,7 +118,7 @@ CDROM_Interface_Image::AudioFile::AudioFile(const char *filename, bool &error)
{
// Use the audio file's actual sample rate and number of channels as opposed to overriding
Sound_AudioInfo desired = {AUDIO_S16, 0, 0};
sample = Sound_NewSampleFromFile(filename, &desired, chunkSize);
sample = Sound_NewSampleFromFile(filename, &desired);
if (sample) {
error = false;
std::string filename_only(filename);
@ -166,11 +162,9 @@ bool CDROM_Interface_Image::AudioFile::seek(Bit32u offset)
return result;
}
Bit16u CDROM_Interface_Image::AudioFile::decode(Bit8u *buffer)
Bit32u CDROM_Interface_Image::AudioFile::decode(Bit16s *buffer, Bit32u desired_track_frames)
{
const Bit16u bytes = Sound_Decode(sample);
memcpy(buffer, sample->buffer, bytes);
return bytes;
return Sound_Decode_Direct(sample, (void*)buffer, desired_track_frames);
}
Bit16u CDROM_Interface_Image::AudioFile::getEndian()
@ -213,27 +207,21 @@ int CDROM_Interface_Image::AudioFile::getLength()
int CDROM_Interface_Image::refCount = 0;
CDROM_Interface_Image* CDROM_Interface_Image::images[26] = {};
CDROM_Interface_Image::imagePlayer CDROM_Interface_Image::player = {
{0}, // buffer[]
{0}, // ctrlData struct
nullptr, // TrackFile*
nullptr, // MixerChannel*
nullptr, // CDROM_Interface_Image*
nullptr, // addSamples
0, // startFrame
0, // currFrame
0, // numFrames
0, // playbackTotal
0, // playbackRemaining
0, // bufferPos
0, // bufferConsumed
false, // isPlaying
false, // isPaused
false // ctrlUsed
{0}, // buffer[]
nullptr, // TrackFile*
nullptr, // MixerChannel*
nullptr, // CDROM_Interface_Image*
nullptr, // addFrames
0, // startRedbookFrame
0, // totalRedbookFrames
0, // playedTrackFrames
0, // totalTrackFrames
false, // isPlaying
false // isPaused
};
CDROM_Interface_Image::CDROM_Interface_Image(Bit8u subUnit)
:subUnit(subUnit)
:subUnit(subUnit)
{
images[subUnit] = this;
if (refCount == 0) {
@ -242,8 +230,7 @@ CDROM_Interface_Image::CDROM_Interface_Image(Bit8u subUnit)
player.channel = MIXER_AddChannel(&CDAudioCallBack, 0, "CDAUDIO");
player.channel->Enable(false);
#ifdef DEBUG
LOG_MSG("CDROM: Initialized with %d-byte circular buffer",
AUDIO_DECODE_BUFFER_SIZE);
LOG_MSG("%s CDROM: Initialized the CDAUDIO mixer channel", get_time());
#endif
}
}
@ -262,7 +249,7 @@ CDROM_Interface_Image::~CDROM_Interface_Image()
MIXER_DelChannel(player.channel);
player.channel = nullptr;
#ifdef DEBUG
LOG_MSG("CDROM: Audio channel freed");
LOG_MSG("%s CDROM: Audio channel freed", get_time());
#endif
}
}
@ -343,38 +330,50 @@ bool CDROM_Interface_Image::GetAudioTrackInfo(int track, TMSF& start, unsigned c
bool CDROM_Interface_Image::GetAudioSub(unsigned char& attr, unsigned char& track,
unsigned char& index, TMSF& relPos, TMSF& absPos)
{
int cur_track = GetTrack(player.currFrame);
if (cur_track < 1) {
return false;
}
track = (unsigned char)cur_track;
attr = tracks[track - 1].attr;
index = 1;
frames_to_msf(player.currFrame + 150, &absPos.min, &absPos.sec, &absPos.fr);
frames_to_msf(player.currFrame - tracks[track - 1].start, &relPos.min, &relPos.sec, &relPos.fr);
bool rcode = false;
// Convert our running tally of track-frames played to Redbook-frames played.
// We round up because if our track-frame tally lands in the middle of a (fractional)
// Redbook frame, then that Redbook frame would have to be considered played to produce
// even the smallest amount of track-frames played. This also accurately represents
// the very end of a sequence where the last Redbook frame might only contain a couple
// PCM samples - but the entire last 2352-byte Redbook frame is needed to cover those samples.
const Bit32u playedRedbookFrames = static_cast<Bit32u>(ceil(
static_cast<float>(REDBOOK_FRAMES_PER_SECOND * player.playedTrackFrames) / player.trackFile->getRate() ));
// Add that to the track's starting Redbook frame to determine our absolute current Redbook frame
const Bit32u currentRedbookFrame = player.startRedbookFrame + playedRedbookFrames;
const char cur_track = GetTrack(currentRedbookFrame);
if (cur_track >= 1) {
track = static_cast<unsigned char>(cur_track);
attr = tracks[track - 1].attr;
index = 1;
frames_to_msf(currentRedbookFrame + 150, &absPos.min, &absPos.sec, &absPos.fr);
frames_to_msf(currentRedbookFrame - tracks[track - 1].start, &relPos.min, &relPos.sec, &relPos.fr);
rcode = true;
#ifdef DEBUG
LOG_MSG("%s CDROM: GetAudioSub attr=%u, track=%u, index=%u",
get_time(),
attr,
track,
index);
LOG_MSG("%s CDROM: GetAudioSub absoute offset (%d), MSF=%d:%d:%d",
get_time(),
player.currFrame + 150,
absPos.min,
absPos.sec,
absPos.fr);
LOG_MSG("%s CDROM: GetAudioSub relative offset (%d), MSF=%d:%d:%d",
get_time(),
player.currFrame - tracks[track - 1].start + 150,
relPos.min,
relPos.sec,
relPos.fr);
LOG_MSG("%s CDROM: GetAudioSub attr=%u, track=%u, index=%u",
get_time(),
attr,
track,
index);
LOG_MSG("%s CDROM: GetAudioSub absoute offset (%d), MSF=%d:%d:%d",
get_time(),
currentRedbookFrame + 150,
absPos.min,
absPos.sec,
absPos.fr);
LOG_MSG("%s CDROM: GetAudioSub relative offset (%d), MSF=%d:%d:%d",
get_time(),
currentRedbookFrame - tracks[track - 1].start + 150,
relPos.min,
relPos.sec,
relPos.fr);
#endif
return true;
}
return rcode;
}
bool CDROM_Interface_Image::GetAudioStatus(bool& playing, bool& pause)
@ -431,7 +430,7 @@ bool CDROM_Interface_Image::PlayAudioSector(unsigned long start, unsigned long l
// We can't play audio from a data track (as it would result in garbage/static)
else if (track >= 0 && tracks[track].attr == 0x40)
LOG(LOG_MISC,LOG_WARN)("Game tries to play the data track. Not doing this");
LOG(LOG_MISC,LOG_WARN)("Game tried to play the data track. Not doing this");
// Checks passed, setup the audio stream
else {
@ -443,55 +442,46 @@ bool CDROM_Interface_Image::PlayAudioSector(unsigned long start, unsigned long l
// only initialize the player elements if our track is playable
if (is_playable) {
const Bit8u channels = trackFile->getChannels();
const Bit32u rate = trackFile->getRate();
const Bit8u track_channels = trackFile->getChannels();
const Bit32u track_rate = trackFile->getRate();
player.cd = this;
player.trackFile = trackFile;
player.startFrame = start;
player.currFrame = start;
player.numFrames = len;
player.bufferPos = 0;
player.bufferConsumed = 0;
player.startRedbookFrame = start;
player.totalRedbookFrames = len;
player.isPlaying = true;
player.isPaused = false;
#if defined(WORDS_BIGENDIAN)
if (trackFile->getEndian() != AUDIO_S16SYS)
#else
if (trackFile->getEndian() == AUDIO_S16SYS)
#endif
player.addSamples = channels == 2 ? &MixerChannel::AddSamples_s16 \
: &MixerChannel::AddSamples_m16;
else
player.addSamples = channels == 2 ? &MixerChannel::AddSamples_s16_nonnative \
: &MixerChannel::AddSamples_m16_nonnative;
const float bytesPerMs = (float) (rate * channels * 2 / 1000.0);
player.playbackTotal = lround(len * tracks[track].sectorSize * bytesPerMs / 176.4);
player.playbackRemaining = player.playbackTotal;
if (trackFile->getEndian() == AUDIO_S16SYS) {
player.addFrames = track_channels == 2 ? &MixerChannel::AddSamples_s16 \
: &MixerChannel::AddSamples_m16;
} else {
player.addFrames = track_channels == 2 ? &MixerChannel::AddSamples_s16_nonnative \
: &MixerChannel::AddSamples_m16_nonnative;
}
// Convert Redbook frames to Track frames, rounding up to whole integer frames.
// Round up to whole track frames because the content originated from whole redbook frames,
// which will require the last fractional frames to be represented by a whole PCM frame.
player.totalTrackFrames = static_cast<Bit32u>(ceil(
static_cast<float>(track_rate * player.totalRedbookFrames) / REDBOOK_FRAMES_PER_SECOND));
player.playedTrackFrames = 0;
#ifdef DEBUG
LOG_MSG("%s CDROM: Playing track %d at %.1f KHz "
"%d-channel at start sector %lu (%.1f minute-mark), seek %u "
"(skip=%d,dstart=%d,secsize=%d), for %lu sectors (%.1f seconds)",
LOG_MSG("%s CDROM: Playing track %d (%d Hz "
"%d-channel) at starting sector %lu (%.1f minute-mark) "
"for %u Redbook frames (%.1f seconds)",
get_time(),
track,
rate/1000.0,
channels,
track_rate,
track_channels,
start,
offset * (1/10584000.0),
offset,
tracks[track].skip,
tracks[track].start,
tracks[track].sectorSize,
len,
player.playbackRemaining / (1000 * bytesPerMs));
static_cast<float>(start) / (REDBOOK_FRAMES_PER_SECOND * 60),
player.totalRedbookFrames,
static_cast<float>(player.totalRedbookFrames) / REDBOOK_FRAMES_PER_SECOND);
#endif
// start the channel!
player.channel->SetFreq(rate);
player.channel->SetFreq(track_rate);
player.channel->Enable(true);
}
}
@ -533,8 +523,15 @@ bool CDROM_Interface_Image::StopAudio(void)
void CDROM_Interface_Image::ChannelControl(TCtrl ctrl)
{
player.ctrlUsed = (ctrl.out[0]!=0 || ctrl.out[1]!=1 || ctrl.vol[0]<0xfe || ctrl.vol[1]<0xfe);
player.ctrlData = ctrl;
if (player.channel == NULL) return;
// Adjust the volume of our mixer channel as defined by the application
player.channel->SetScale(static_cast<float>(ctrl.vol[0]/255.0), // left vol
static_cast<float>(ctrl.vol[1]/255.0)); // right vol
// Map the audio channels in our mixer channel as defined by the application
player.channel->MapChannels(ctrl.out[0], // left map
ctrl.out[1]); // right map
}
bool CDROM_Interface_Image::ReadSectors(PhysPt buffer, bool raw, unsigned long sector, unsigned long num)
@ -590,137 +587,47 @@ bool CDROM_Interface_Image::ReadSector(Bit8u *buffer, bool raw, unsigned long se
if (tracks[track].sectorSize == RAW_SECTOR_SIZE && !tracks[track].mode2 && !raw) seek += 16;
if (tracks[track].mode2 && !raw) seek += 24;
#if 0 // Excessively verbose.. only enable if needed
#ifdef DEBUG
LOG_MSG("CDROM: ReadSector track=%d, desired raw=%s, sector=%ld, length=%d",
LOG_MSG("%s CDROM: ReadSector track=%d, desired raw=%s, sector=%ld, length=%d",
get_time(),
track,
raw ? "true":"false",
sector,
length);
#endif
#endif
return tracks[track].file->read(buffer, seek, length);
}
void CDROM_Interface_Image::CDAudioCallBack(Bitu len)
void CDROM_Interface_Image::CDAudioCallBack(Bitu desired_track_frames)
{
// Our member object "playbackRemaining" holds the
// exact number of stream-bytes we need to play before meeting the
// DOS program's desired playback duration in sectors. We simply
// decrement this counter each callback until we're done.
if (len == 0 || !player.isPlaying || player.isPaused) {
return;
}
if (desired_track_frames > 0) {
// determine bytes per request (16-bit samples)
const Bit8u channels = player.trackFile->getChannels();
const Bit8u bytes_per_request = channels * 2;
int total_requested = len * bytes_per_request;
const Bit32u decoded_track_frames = player.trackFile->decode(player.buffer, desired_track_frames);
while (total_requested > 0) {
int requested = total_requested;
// uses either the stereo or mono and native or nonnative AddSamples call assigned during construction
(player.channel->*player.addFrames)(decoded_track_frames, player.buffer);
// Every now and then the callback wants a big number of bytes,
// which can exceed our circular buffer. In these cases we need
// read through as many iteration of our circular buffer as needed.
if (total_requested > AUDIO_DECODE_BUFFER_SIZE) {
requested = AUDIO_DECODE_BUFFER_SIZE;
total_requested -= AUDIO_DECODE_BUFFER_SIZE;
}
else {
total_requested = 0;
// Stop the audio if our decode stream has run dry or when we've played at least the
// total number of frames. We consider "played" to mean the running tally so far plus
// the current requested frames, which takes into account that the number of currently
// decoded frames might be less than desired if we're at the end of the track.
// (The mixer will request frames forever until we stop it, so at some point we /will/
// decode fewer than requested; in this "tail scenario", we push those remaining decoded
// frames into the mixer and then stop the audio.)
if (decoded_track_frames == 0
|| player.playedTrackFrames + desired_track_frames >= player.totalTrackFrames) {
player.cd->StopAudio();
}
// Three scenarios in order of probabilty:
//
// 1. Consume: If our decoded circular buffer is sufficiently filled to
// satify the Mixer's requested size, then feed the Mixer with
// the requested number of bytes.
//
// 2. Wrap: If we've decoded and consumed to edge of our buffer, then
// we need to wrap any remaining decoded-but-not-consumed
// samples back around to the front of the buffer.
//
// 3. Fill: When our circular buffer is too depleted to satisfy the
// Mixer's requested size, then ask the decoder for more data;
// to either enough to completely fill our buffer or whatever
// is left to satisfy the remaining samples in this requested
// playback sequence (the Mixer is unaware of how much audio
// xwe need to play; instead we decide when to cutoff the mixer).
//
while (true) {
// 1. Consume
if (player.bufferPos - player.bufferConsumed >= requested) {
if (player.ctrlUsed) {
for (Bit8u i=0; i < channels; i++) {
Bit16s sample;
Bit16s* samples = (Bit16s*)&player.buffer[player.bufferConsumed];
for (int pos = 0; pos < requested / bytes_per_request; pos++) {
#if defined(WORDS_BIGENDIAN)
sample = (Bit16s)host_readw((HostPt) & samples[pos * 2 + player.ctrlData.out[i]]);
#else
sample = samples[pos * 2 + player.ctrlData.out[i]];
#endif
samples[pos * 2 + i] = (Bit16s)(sample * player.ctrlData.vol[i] / 255.0);
}
}
}
// uses either the stereo or mono and native or nonnative AddSamples
// call assigned during construction
(player.channel->*player.addSamples)(requested / bytes_per_request,
(Bit16s*)(player.buffer + player.bufferConsumed) );
player.bufferConsumed += requested;
player.playbackRemaining -= requested;
// Games can query the current Red Book MSF frame-position, so we keep that up-to-date here.
// We scale the final number of frames by the percent complete, which
// avoids having to keep track of the equivlent number of Red Book frames
// read (which would involve coverting the compressed streams data-rate into
// CDROM Red Book rate, which is more work than simply scaling).
//
const double playbackPercentSoFar = (player.playbackTotal - player.playbackRemaining) / player.playbackTotal;
player.currFrame = player.startFrame + (Bit32u) ceil(player.numFrames * playbackPercentSoFar);
break;
}
// 2. Wrap
else {
memcpy(player.buffer,
player.buffer + player.bufferConsumed,
player.bufferPos - player.bufferConsumed);
player.bufferPos -= player.bufferConsumed;
player.bufferConsumed = 0;
}
// 3. Fill
const Bit16u chunkSize = player.trackFile->chunkSize;
while(AUDIO_DECODE_BUFFER_SIZE - player.bufferPos >= chunkSize &&
(player.bufferPos - player.bufferConsumed < player.playbackRemaining ||
player.bufferPos - player.bufferConsumed < requested) ) {
const Bit16u decoded = player.trackFile->decode(player.buffer + player.bufferPos);
player.bufferPos += decoded;
// if we decoded less than expected, which could be due to EOF or if the CUE file specified
// an exact "INDEX 01 MIN:SEC:FRAMES" value but the compressed track is ever-so-slightly less than
// that specified, then simply pad with zeros.
const Bit16s underDecode = chunkSize - decoded;
if (underDecode > 0) {
#ifdef DEBUG
LOG_MSG("%s CDROM: Underdecoded by %d. Feeding mixer with zeros.",
get_time(),
underDecode);
#endif
memset(player.buffer + player.bufferPos, 0, underDecode);
player.bufferPos += underDecode;
}
} // end of fill-while
} // end of decode and fill loop
} // end while total_requested
if (player.playbackRemaining <= 0) {
player.cd->StopAudio();
// Increment our tally of played frames by those we just decoded (and fed to the mixer).
// Even if we've hit the end of the track and we've stopped the audio, we still want to
// increment our tally so subsequent calls to GetAudioSub() return the full number of
// frames played.
player.playedTrackFrames += decoded_track_frames;
}
}

View file

@ -470,8 +470,15 @@ bool CDROM_Interface_Ioctl::StopAudio(void) {
void CDROM_Interface_Ioctl::ChannelControl(TCtrl ctrl)
{
player.ctrlUsed = (ctrl.out[0]!=0 || ctrl.out[1]!=1 || ctrl.vol[0]<0xfe || ctrl.vol[1]<0xfe);
player.ctrlData = ctrl;
if (player.channel == NULL) return;
// Adjust the volme of our mixer channel as defined by the application
player.channel->SetScale(static_cast<float>(ctrl.vol[0]/255.0), // left vol
static_cast<float>(ctrl.vol[1]/255.0)); // right vol
// Map the audio channels in our mixer channel as defined by the application
player.channel->MapChannels(ctrl.out[0], // left map
ctrl.out[1]); // right map
}
bool CDROM_Interface_Ioctl::LoadUnloadMedia(bool unload) {
@ -565,16 +572,6 @@ void CDROM_Interface_Ioctl::dx_CDAudioCallBack(Bitu len) {
}
}
SDL_mutexV(player.mutex);
if (player.ctrlUsed) {
Bit16s sample0,sample1;
Bit16s * samples=(Bit16s *)&player.buffer;
for (Bitu pos=0;pos<len/4;pos++) {
sample0=samples[pos*2+player.ctrlData.out[0]];
sample1=samples[pos*2+player.ctrlData.out[1]];
samples[pos*2+0]=(Bit16s)(sample0*player.ctrlData.vol[0]/255.0);
samples[pos*2+1]=(Bit16s)(sample1*player.ctrlData.vol[1]/255.0);
}
}
player.channel->AddSamples_s16(len/4,(Bit16s *)player.buffer);
memmove(player.buffer, &player.buffer[len], player.bufLen - len);
player.bufLen -= len;

View file

@ -22,15 +22,20 @@
That should call the mixer start from there or something.
*/
// #define DEBUG 1
#include <string.h>
#include <sys/types.h>
#include <math.h>
#include <algorithm>
#if defined (WIN32)
//Midi listing
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
// prevent the Windows header from clobbering std::min and max
#define NOMINMAX
#include <windows.h>
#include <mmsystem.h>
#endif
@ -90,16 +95,35 @@ static struct {
Bit8u MixTemp[MIXER_BUFSIZE];
MixerChannel * MIXER_AddChannel(MIXER_Handler handler,Bitu freq,const char * name) {
MixerChannel * chan=new MixerChannel();
chan->scale = 1.0;
chan->handler=handler;
chan->name=name;
chan->SetFreq(freq);
MixerChannel::MixerChannel(MIXER_Handler _handler, Bitu _freq, const char * _name) :
// Public member initialization
volmain {0, 0},
next (nullptr),
name (_name),
done (0),
enabled (false),
// Private member initialization
handler (_handler),
freq_add (0),
freq_counter (0),
needed (0),
prev_sample {0, 0},
next_sample {0, 0},
volmul {0, 0},
scale {0, 0},
channel_map {0, 0},
interpolate (false) {
}
MixerChannel * MIXER_AddChannel(MIXER_Handler handler, Bitu freq, const char * name) {
MixerChannel * chan=new MixerChannel(handler, freq, name);
chan->next=mixer.channels;
chan->SetVolume(1,1);
chan->enabled=false;
chan->interpolate = false;
chan->SetFreq(freq); // also enables 'interpolate' if needed
chan->SetScale(1.0);
chan->SetVolume(1, 1);
chan->MapChannels(0, 1);
chan->Enable(false);
mixer.channels=chan;
return chan;
}
@ -128,19 +152,59 @@ void MIXER_DelChannel(MixerChannel* delchan) {
}
void MixerChannel::UpdateVolume(void) {
volmul[0]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[0]*mixer.mastervol[0]);
volmul[1]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[1]*mixer.mastervol[1]);
volmul[0]=(Bits)((1 << MIXER_VOLSHIFT)*scale[0]*volmain[0]*mixer.mastervol[0]);
volmul[1]=(Bits)((1 << MIXER_VOLSHIFT)*scale[1]*volmain[1]*mixer.mastervol[1]);
}
template <typename T>
T clamp(const T& n, const T& lower, const T& upper) {
return std::max(lower, std::min(n, upper));
}
void MixerChannel::SetVolume(float _left,float _right) {
volmain[0]=_left;
volmain[1]=_right;
// Allow unconstrained user-defined values
volmain[0] = _left;
volmain[1] = _right;
UpdateVolume();
}
void MixerChannel::SetScale( float f ) {
scale = f;
UpdateVolume();
void MixerChannel::SetScale(float f) {
SetScale(f, f);
}
void MixerChannel::SetScale(float _left, float _right) {
// Constrain application-defined volume between 0% and 100%
const float min_volume(0.0);
const float max_volume(1.0);
_left = clamp(_left, min_volume, max_volume);
_right = clamp(_right, min_volume, max_volume);
if (scale[0] != _left || scale[1] != _right) {
scale[0] = _left;
scale[1] = _right;
UpdateVolume();
#ifdef DEBUG
LOG_MSG("MIXER %-7s channel: application changed left and right volumes to %3.0f%% and %3.0f%%, respectively",
name, scale[0] * 100, scale[1] * 100);
#endif
}
}
void MixerChannel::MapChannels(Bit8u _left, Bit8u _right) {
// Constrain mapping to the 0 (left) and 1 (right) channel indexes
const Bit8u min_channel(0);
const Bit8u max_channel(1);
_left = clamp(_left, min_channel, max_channel);
_right = clamp(_right, min_channel, max_channel);
if (channel_map[0] != _left || channel_map[1] != _right) {
channel_map[0] = _left;
channel_map[1] = _right;
#ifdef DEBUG
LOG_MSG("MIXER %-7s channel: application changed audio-channel mapping to left=>%s and right=>%s",
name,
channel_map[0] == 0 ? "left" : "right",
channel_map[1] == 0 ? "left" : "right");
#endif
}
}
void MixerChannel::Enable(bool _yesno) {
@ -179,8 +243,8 @@ void MixerChannel::AddSilence(void) {
if (done<needed) {
done=needed;
//Make sure the next samples are zero when they get switched to prev
nextSample[0] = 0;
nextSample[1] = 0;
next_sample[0] = 0;
next_sample[1] = 0;
//This should trigger an instant request for new samples
freq_counter = FREQ_NEXT;
}
@ -200,24 +264,26 @@ inline void MixerChannel::AddSamples(Bitu len, const Type* data) {
if (pos >= len)
return;
freq_counter -= FREQ_NEXT;
prevSample[0] = nextSample[0];
prev_sample[0] = next_sample[0];
if (stereo) {
prevSample[1] = nextSample[1];
prev_sample[1] = next_sample[1];
}
if ( sizeof( Type) == 1) {
if (!signeddata) {
if (stereo) {
nextSample[0]=(((Bit8s)(data[pos*2+0] ^ 0x80)) << 8);
nextSample[1]=(((Bit8s)(data[pos*2+1] ^ 0x80)) << 8);
next_sample[0]=(((Bit8s)(data[pos*2+0] ^ 0x80)) << 8);
next_sample[1]=(((Bit8s)(data[pos*2+1] ^ 0x80)) << 8);
} else {
nextSample[0]=(((Bit8s)(data[pos] ^ 0x80)) << 8);
next_sample[0]=(((Bit8s)(data[pos] ^ 0x80)) << 8);
}
} else {
if (stereo) {
nextSample[0]=(data[pos*2+0] << 8);
nextSample[1]=(data[pos*2+1] << 8);
next_sample[0]=(data[pos*2+0] << 8);
next_sample[1]=(data[pos*2+1] << 8);
} else {
nextSample[0]=(data[pos] << 8);
next_sample[0]=(data[pos] << 8);
}
}
//16bit and 32bit both contain 16bit data internally
@ -225,50 +291,50 @@ inline void MixerChannel::AddSamples(Bitu len, const Type* data) {
if (signeddata) {
if (stereo) {
if (nativeorder) {
nextSample[0]=data[pos*2+0];
nextSample[1]=data[pos*2+1];
next_sample[0]=data[pos*2+0];
next_sample[1]=data[pos*2+1];
} else {
if ( sizeof( Type) == 2) {
nextSample[0]=(Bit16s)host_readw((HostPt)&data[pos*2+0]);
nextSample[1]=(Bit16s)host_readw((HostPt)&data[pos*2+1]);
next_sample[0]=(Bit16s)host_readw((HostPt)&data[pos*2+0]);
next_sample[1]=(Bit16s)host_readw((HostPt)&data[pos*2+1]);
} else {
nextSample[0]=(Bit32s)host_readd((HostPt)&data[pos*2+0]);
nextSample[1]=(Bit32s)host_readd((HostPt)&data[pos*2+1]);
next_sample[0]=(Bit32s)host_readd((HostPt)&data[pos*2+0]);
next_sample[1]=(Bit32s)host_readd((HostPt)&data[pos*2+1]);
}
}
} else {
if (nativeorder) {
nextSample[0] = data[pos];
next_sample[0] = data[pos];
} else {
if ( sizeof( Type) == 2) {
nextSample[0]=(Bit16s)host_readw((HostPt)&data[pos]);
next_sample[0]=(Bit16s)host_readw((HostPt)&data[pos]);
} else {
nextSample[0]=(Bit32s)host_readd((HostPt)&data[pos]);
next_sample[0]=(Bit32s)host_readd((HostPt)&data[pos]);
}
}
}
} else {
if (stereo) {
if (nativeorder) {
nextSample[0]=(Bits)data[pos*2+0]-32768;
nextSample[1]=(Bits)data[pos*2+1]-32768;
next_sample[0]=(Bits)data[pos*2+0]-32768;
next_sample[1]=(Bits)data[pos*2+1]-32768;
} else {
if ( sizeof( Type) == 2) {
nextSample[0]=(Bits)host_readw((HostPt)&data[pos*2+0])-32768;
nextSample[1]=(Bits)host_readw((HostPt)&data[pos*2+1])-32768;
next_sample[0]=(Bits)host_readw((HostPt)&data[pos*2+0])-32768;
next_sample[1]=(Bits)host_readw((HostPt)&data[pos*2+1])-32768;
} else {
nextSample[0]=(Bits)host_readd((HostPt)&data[pos*2+0])-32768;
nextSample[1]=(Bits)host_readd((HostPt)&data[pos*2+1])-32768;
next_sample[0]=(Bits)host_readd((HostPt)&data[pos*2+0])-32768;
next_sample[1]=(Bits)host_readd((HostPt)&data[pos*2+1])-32768;
}
}
} else {
if (nativeorder) {
nextSample[0]=(Bits)data[pos]-32768;
next_sample[0]=(Bits)data[pos]-32768;
} else {
if ( sizeof( Type) == 2) {
nextSample[0]=(Bits)host_readw((HostPt)&data[pos])-32768;
next_sample[0]=(Bits)host_readw((HostPt)&data[pos])-32768;
} else {
nextSample[0]=(Bits)host_readd((HostPt)&data[pos])-32768;
next_sample[0]=(Bits)host_readd((HostPt)&data[pos])-32768;
}
}
}
@ -277,19 +343,28 @@ inline void MixerChannel::AddSamples(Bitu len, const Type* data) {
//This sample has been handled now, increase position
pos++;
}
//Apply the left and right channel mappers only on write[..]
//assignments. This ensures the channels are mapped only once
//(avoiding double-swapping) and also minimizes the places where
//we use our mapping variables as array indexes.
//Note that volumes are independent of the channels mapping.
const Bit8u left_map(channel_map[0]);
const Bit8u right_map(channel_map[1]);
//Where to write
mixpos &= MIXER_BUFMASK;
Bit32s* write = mixer.work[mixpos];
if (!interpolate) {
write[0] += prevSample[0] * volmul[0];
write[1] += (stereo ? prevSample[1] : prevSample[0]) * volmul[1];
write[0] += prev_sample[left_map] * volmul[0];
write[1] += (stereo ? prev_sample[right_map] : prev_sample[left_map]) * volmul[1];
}
else {
Bits diff_mul = freq_counter & FREQ_MASK;
Bits sample = prevSample[0] + (((nextSample[0] - prevSample[0]) * diff_mul) >> FREQ_SHIFT);
Bits sample = prev_sample[left_map] + (((next_sample[left_map] - prev_sample[left_map]) * diff_mul) >> FREQ_SHIFT);
write[0] += sample*volmul[0];
if (stereo) {
sample = prevSample[1] + (((nextSample[1] - prevSample[1]) * diff_mul) >> FREQ_SHIFT);
sample = prev_sample[right_map] + (((next_sample[right_map] - prev_sample[right_map]) * diff_mul) >> FREQ_SHIFT);
}
write[1] += sample*volmul[1];
}
@ -318,14 +393,14 @@ void MixerChannel::AddStretched(Bitu len,Bit16s * data) {
if (pos != new_pos) {
pos = new_pos;
//Forward the previous sample
prevSample[0] = data[0];
prev_sample[0] = data[0];
data++;
}
Bits diff = data[0] - prevSample[0];
Bits diff = data[0] - prev_sample[0];
Bits diff_mul = index & FREQ_MASK;
index += index_add;
mixpos &= MIXER_BUFMASK;
Bits sample = prevSample[0] + ((diff * diff_mul) >> FREQ_SHIFT);
Bits sample = prev_sample[0] + ((diff * diff_mul) >> FREQ_SHIFT);
mixer.work[mixpos][0] += sample * volmul[0];
mixer.work[mixpos][1] += sample * volmul[1];
mixpos++;
@ -702,7 +777,7 @@ void MIXER_Init(Section* sec) {
mixer.tick_add=calc_tickadd(mixer.freq);
TIMER_AddTickHandler(MIXER_Mix_NoSound);
} else {
if((mixer.freq != obtained.freq) || (mixer.blocksize != obtained.samples))
if((static_cast<Bit16s>(mixer.freq) != obtained.freq) || (mixer.blocksize != obtained.samples))
LOG_MSG("MIXER: Got different values from SDL: freq %d, blocksize %d",obtained.freq,obtained.samples);
mixer.freq=obtained.freq;
mixer.blocksize=obtained.samples;