Merge branch 'feature/precise-audio'

This commit is contained in:
Christian Speckner 2018-07-01 23:48:26 +02:00
commit c270a45409
66 changed files with 3863 additions and 1663 deletions

35
.vscode/settings.json vendored
View File

@ -26,6 +26,39 @@
"string_view": "cpp",
"system_error": "cpp",
"vector": "cpp",
"stdexcept": "cpp"
"sstream": "cpp",
"__bit_reference": "cpp",
"__functional_base": "cpp",
"algorithm": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"functional": "cpp",
"iterator": "cpp",
"limits": "cpp",
"locale": "cpp",
"memory": "cpp",
"ratio": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"stdexcept": "cpp",
"fstream": "cpp",
"__locale": "cpp",
"__string": "cpp",
"__config": "cpp",
"__nullptr": "cpp",
"cstddef": "cpp",
"exception": "cpp",
"initializer_list": "cpp",
"new": "cpp",
"typeinfo": "cpp",
"__mutex_base": "cpp",
"mutex": "cpp",
"condition_variable": "cpp",
"*.ins": "cpp",
"cstring": "cpp",
"iostream": "cpp",
"cstdint": "cpp",
"ostream": "cpp",
"__memory": "cpp"
}
}

View File

@ -101,7 +101,8 @@ MODULES += \
src/emucore/tia/frame-manager \
src/gui \
src/common \
src/common/tv_filters
src/common/tv_filters \
src/common/audio
######################################################################
# The build rules follow - normally you should have no need to

9
TODO_audio.md Normal file
View File

@ -0,0 +1,9 @@
# Missing features
* Reimplement target FPS mode
* Add GUI for new audio parameters (prebuffer fragment count, resampling quality)
# Cleanup
* Remove or turn sterr output into log messages
* Document EmulationTiming

142
src/common/AudioQueue.cxx Normal file
View File

@ -0,0 +1,142 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AudioQueue.hxx"
using std::mutex;
using std::lock_guard;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo)
: myFragmentSize(fragmentSize),
myIsStereo(isStereo),
myFragmentQueue(capacity),
myAllFragments(capacity + 2),
mySize(0),
myNextFragment(0)
{
const uInt8 sampleSize = myIsStereo ? 2 : 1;
myFragmentBuffer = make_unique<Int16[]>(myFragmentSize * sampleSize * (capacity + 2));
for (uInt32 i = 0; i < capacity; i++)
myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer.get() + i * sampleSize * myFragmentSize;
myAllFragments[capacity] = myFirstFragmentForEnqueue =
myFragmentBuffer.get() + capacity * sampleSize * myFragmentSize;
myAllFragments[capacity + 1] = myFirstFragmentForDequeue =
myFragmentBuffer.get() + (capacity + 1) * sampleSize * myFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioQueue::capacity() const
{
return uInt32(myFragmentQueue.size());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioQueue::size()
{
lock_guard<mutex> guard(myMutex);
return mySize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioQueue::isStereo() const
{
return myIsStereo;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioQueue::fragmentSize() const
{
return myFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int16* AudioQueue::enqueue(Int16* fragment)
{
lock_guard<mutex> guard(myMutex);
Int16* newFragment;
if (!fragment) {
if (!myFirstFragmentForEnqueue) throw runtime_error("enqueue called empty");
newFragment = myFirstFragmentForEnqueue;
myFirstFragmentForEnqueue = nullptr;
return newFragment;
}
const uInt8 capacity = myFragmentQueue.size();
const uInt8 fragmentIndex = (myNextFragment + mySize) % capacity;
newFragment = myFragmentQueue.at(fragmentIndex);
myFragmentQueue.at(fragmentIndex) = fragment;
if (mySize < capacity) mySize++;
else {
myNextFragment = (myNextFragment + 1) % capacity;
if (!myIgnoreOverflows) (cerr << "audio buffer overflow\n").flush();
}
return newFragment;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int16* AudioQueue::dequeue(Int16* fragment)
{
lock_guard<mutex> guard(myMutex);
if (mySize == 0) return nullptr;
if (!fragment) {
if (!myFirstFragmentForDequeue) throw runtime_error("dequeue called empty");
fragment = myFirstFragmentForDequeue;
myFirstFragmentForDequeue = nullptr;
}
Int16* nextFragment = myFragmentQueue.at(myNextFragment);
myFragmentQueue.at(myNextFragment) = fragment;
mySize--;
myNextFragment = (myNextFragment + 1) % myFragmentQueue.size();
return nextFragment;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioQueue::closeSink(Int16* fragment)
{
lock_guard<mutex> guard(myMutex);
if (myFirstFragmentForDequeue && fragment)
throw new runtime_error("attempt to return unknown buffer on closeSink");
if (!myFirstFragmentForDequeue)
myFirstFragmentForDequeue = fragment;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioQueue::ignoreOverflows(bool shouldIgnoreOverflows)
{
myIgnoreOverflows = shouldIgnoreOverflows;
}

142
src/common/AudioQueue.hxx Normal file
View File

@ -0,0 +1,142 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef AUDIO_QUEUE_HXX
#define AUDIO_QUEUE_HXX
#include <mutex>
#include "bspf.hxx"
/**
This class implements a an audio queue that acts both like a ring buffer
and a pool of audio fragments. The TIA emulation core fills a fragment
with samples and then returns it to the queue, receiving a new fragment
in return. The sound driver removes fragments for playback from the
queue and returns the used fragment in this process.
The queue needs to be threadsafe as the (SDL) audio driver runs on a
separate thread. Samples are stored as signed 16 bit integers
(platform endian).
*/
class AudioQueue
{
public:
/**
Create a new AudioQueue.
@param fragmentSize The size (in stereo / mono samples) of each fragment
@param capacity The number of fragments that can be queued before wrapping.
@param isStereo Whether samples are stereo or mono.
@param sampleRate The sample rate. This is not used, but can be queried.
*/
AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo);
/**
Capacity getter.
*/
uInt32 capacity() const;
/**
Size getter.
*/
uInt32 size();
/**
Stereo / mono getter.
*/
bool isStereo() const;
/**
Fragment size getter.
*/
uInt32 fragmentSize() const;
/**
Enqueue a new fragment and get a new fragmen to fill.
@param fragment The returned fragment. This must be empty on the first call (when
there is nothing to return)
*/
Int16* enqueue(Int16* fragment = nullptr);
/**
Dequeue a fragment for playback and return the played fragment. This may
return 0 if there is no queued fragment to return (in this case, the returned
fragment is not enqueued and must be passed in the next invocation).
@param fragment The returned fragment. This must be empty on the first call (when
there is nothing to return).
*/
Int16* dequeue(Int16* fragment = nullptr);
/**
Return the currently playing fragment without drawing a new one. This is called
if the sink is closed and prepares the queue to be reopened.
*/
void closeSink(Int16* fragment);
/**
Should we ignore overflows?
*/
void ignoreOverflows(bool shouldIgnoreOverflows);
private:
// The size of an individual fragment (in stereo / mono samples)
uInt32 myFragmentSize;
// Are we using stereo samples?
bool myIsStereo;
// The fragment queue
vector<Int16*> myFragmentQueue;
// All fragments, including the two fragments that are in circulation.
vector<Int16*> myAllFragments;
// We allocate a consecutive slice of memory for the fragments.
unique_ptr<Int16[]> myFragmentBuffer;
// The nubmer if queued fragments
uInt32 mySize;
// The next fragment.
uInt32 myNextFragment;
// We need a mutex for thread safety.
std::mutex myMutex;
// The first (empty) enqueue call returns this fragment.
Int16* myFirstFragmentForEnqueue;
// The first (empty) dequeue call replaces the returned fragment with this fragment.
Int16* myFirstFragmentForDequeue;
// Log overflows?
bool myIgnoreOverflows;
private:
AudioQueue() = delete;
AudioQueue(const AudioQueue&) = delete;
AudioQueue(AudioQueue&&) = delete;
AudioQueue& operator=(const AudioQueue&) = delete;
AudioQueue& operator=(AudioQueue&&) = delete;
};
#endif // AUDIO_QUEUE_HXX

View File

@ -0,0 +1,291 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AudioSettings.hxx"
#include "Settings.hxx"
namespace {
uInt32 convertInt(int x, int defaultValue)
{
return x <= defaultValue ? defaultValue : x;
}
AudioSettings::Preset normalizedPreset(int numericPreset)
{
return (
numericPreset >= static_cast<int>(AudioSettings::Preset::custom) &&
numericPreset <= static_cast<int>(AudioSettings::Preset::veryHighQualityVeryLowLag)
) ? static_cast<AudioSettings::Preset>(numericPreset) : AudioSettings::DEFAULT_PRESET;
}
AudioSettings::ResamplingQuality normalizeResamplingQuality(int numericResamplingQuality)
{
return (
numericResamplingQuality >= static_cast<int>(AudioSettings::ResamplingQuality::nearestNeightbour) &&
numericResamplingQuality <= static_cast<int>(AudioSettings::ResamplingQuality::lanczos_3)
) ? static_cast<AudioSettings::ResamplingQuality>(numericResamplingQuality) : AudioSettings::DEFAULT_RESAMPLING_QUALITY;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioSettings::AudioSettings()
: mySettings(),
myIsPersistent(false)
{}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioSettings::AudioSettings(Settings* settings)
: mySettings(settings),
myIsPersistent(true)
{
setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET)));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::normalize(Settings& settings)
{
int settingPreset = settings.getInt(SETTING_PRESET);
Preset preset = normalizedPreset(settingPreset);
if (static_cast<int>(preset) != settingPreset) settings.setValue(SETTING_PRESET, static_cast<int>(DEFAULT_PRESET));
switch (settings.getInt(SETTING_SAMPLE_RATE)) {
case 44100:
case 48000:
case 96000:
break;
default:
settings.setValue(SETTING_SAMPLE_RATE, DEFAULT_SAMPLE_RATE);
break;
}
switch (settings.getInt(SETTING_FRAGMENT_SIZE)) {
case 128:
case 256:
case 512:
case 1024:
case 2048:
case 4096:
break;
default:
settings.setValue(SETTING_FRAGMENT_SIZE, DEFAULT_FRAGMENT_SIZE);
break;
}
int settingBufferSize = settings.getInt(SETTING_BUFFER_SIZE);
if (settingBufferSize < 0 || settingBufferSize > MAX_BUFFER_SIZE) settings.setValue(SETTING_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
int settingHeadroom = settings.getInt(SETTING_HEADROOM);
if (settingHeadroom < 0 || settingHeadroom > MAX_HEADROOM) settings.setValue(SETTING_HEADROOM, DEFAULT_HEADROOM);
int settingResamplingQuality = settings.getInt(SETTING_RESAMPLING_QUALITY);
ResamplingQuality resamplingQuality = normalizeResamplingQuality(settingResamplingQuality);
if (static_cast<int>(resamplingQuality) != settingResamplingQuality)
settings.setValue(SETTING_RESAMPLING_QUALITY, static_cast<int>(DEFAULT_RESAMPLING_QUALITY));
int settingVolume = settings.getInt(SETTING_VOLUME);
if (settingVolume < 0 || settingVolume > 100) settings.setValue(SETTING_VOLUME, DEFAULT_VOLUME);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioSettings::Preset AudioSettings::preset()
{
updatePresetFromSettings();
return myPreset;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::sampleRate()
{
updatePresetFromSettings();
return customSettings() ? convertInt(mySettings->getInt(SETTING_SAMPLE_RATE), DEFAULT_SAMPLE_RATE) : myPresetSampleRate;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::fragmentSize()
{
updatePresetFromSettings();
return customSettings() ? convertInt(mySettings->getInt(SETTING_FRAGMENT_SIZE), DEFAULT_FRAGMENT_SIZE) : myPresetFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::bufferSize()
{
updatePresetFromSettings();
// 0 is a valid value -> keep it
return customSettings() ? convertInt(mySettings->getInt(SETTING_BUFFER_SIZE), 0) : myPresetBufferSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::headroom()
{
updatePresetFromSettings();
// 0 is a valid value -> keep it
return customSettings() ? convertInt(mySettings->getInt(SETTING_HEADROOM), 0) : myPresetHeadroom;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioSettings::ResamplingQuality AudioSettings::resamplingQuality()
{
updatePresetFromSettings();
return customSettings() ? normalizeResamplingQuality(mySettings->getInt(SETTING_RESAMPLING_QUALITY)) : myPresetResamplingQuality;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::volume() const
{
// 0 is a valid value -> keep it
return convertInt(mySettings->getInt(SETTING_VOLUME), 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioSettings::enabled() const
{
return mySettings->getBool(SETTING_ENABLED);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setPreset(AudioSettings::Preset preset)
{
if (preset == myPreset) return;
myPreset = preset;
switch (myPreset) {
case Preset::custom:
break;
case Preset::lowQualityMediumLag:
myPresetSampleRate = 44100;
myPresetFragmentSize = 1024;
myPresetBufferSize = 6;
myPresetHeadroom = 5;
myPresetResamplingQuality = ResamplingQuality::nearestNeightbour;
break;
case Preset::highQualityMediumLag:
myPresetSampleRate = 44100;
myPresetFragmentSize = 1024;
myPresetBufferSize = 6;
myPresetHeadroom = 5;
myPresetResamplingQuality = ResamplingQuality::lanczos_2;
break;
case Preset::highQualityLowLag:
myPresetSampleRate = 48000;
myPresetFragmentSize = 512;
myPresetBufferSize = 3;
myPresetHeadroom = 2;
myPresetResamplingQuality = ResamplingQuality::lanczos_2;
break;
case Preset::veryHighQualityVeryLowLag:
myPresetSampleRate = 96000;
myPresetFragmentSize = 128;
myPresetBufferSize = 0;
myPresetHeadroom = 0;
myPresetResamplingQuality = ResamplingQuality::lanczos_3;
break;
default:
throw runtime_error("invalid preset");
}
if (myIsPersistent) mySettings->setValue(SETTING_PRESET, static_cast<int>(myPreset));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setSampleRate(uInt32 sampleRate)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_SAMPLE_RATE, sampleRate);
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setFragmentSize(uInt32 fragmentSize)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_FRAGMENT_SIZE, fragmentSize);
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setBufferSize(uInt32 bufferSize)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_BUFFER_SIZE, bufferSize);
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setHeadroom(uInt32 headroom)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_HEADROOM, headroom);
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setResamplingQuality(AudioSettings::ResamplingQuality resamplingQuality)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_RESAMPLING_QUALITY, static_cast<int>(resamplingQuality));
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setVolume(uInt32 volume)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_VOLUME, volume);
normalize(*mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setEnabled(bool isEnabled)
{
if (!myIsPersistent) return;
mySettings->setValue(SETTING_ENABLED, isEnabled);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setPersistent(bool isPersistent)
{
myIsPersistent = isPersistent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioSettings::customSettings() const
{
return myPreset == Preset::custom;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::updatePresetFromSettings()
{
if (!myIsPersistent) return;
setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET)));
}

View File

@ -0,0 +1,129 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef AUDIO_PARAMTERS_HXX
#define AUDIO_PARAMTERS_HXX
#include "bspf.hxx"
class Settings;
class AudioSettings
{
public:
enum class Preset {
custom = 1,
lowQualityMediumLag = 2,
highQualityMediumLag = 3,
highQualityLowLag = 4,
veryHighQualityVeryLowLag = 5
};
enum class ResamplingQuality {
nearestNeightbour = 1,
lanczos_2 = 2,
lanczos_3 = 3
};
static constexpr const char* SETTING_PRESET = "audio.preset";
static constexpr const char* SETTING_SAMPLE_RATE = "audio.sample_rate";
static constexpr const char* SETTING_FRAGMENT_SIZE = "audio.fragment_size";
static constexpr const char* SETTING_BUFFER_SIZE = "audio.buffer_size";
static constexpr const char* SETTING_HEADROOM = "audio.headroom";
static constexpr const char* SETTING_RESAMPLING_QUALITY = "audio.resampling_quality";
static constexpr const char* SETTING_VOLUME = "audio.volume";
static constexpr const char* SETTING_ENABLED = "audio.enabled";
static constexpr Preset DEFAULT_PRESET = Preset::highQualityMediumLag;
static constexpr uInt32 DEFAULT_SAMPLE_RATE = 44100;
static constexpr uInt32 DEFAULT_FRAGMENT_SIZE = 512;
static constexpr uInt32 DEFAULT_BUFFER_SIZE = 3;
static constexpr uInt32 DEFAULT_HEADROOM = 2;
static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2;
static constexpr uInt32 DEFAULT_VOLUME = 80;
static constexpr bool DEFAULT_ENABLED = true;
static constexpr int MAX_BUFFER_SIZE = 10;
static constexpr int MAX_HEADROOM = 10;
public:
AudioSettings();
AudioSettings(Settings* mySettings);
static void initialize(Settings& settings);
static void normalize(Settings& settings);
Preset preset();
uInt32 sampleRate();
uInt32 fragmentSize();
uInt32 bufferSize();
uInt32 headroom();
ResamplingQuality resamplingQuality();
uInt32 volume() const;
bool enabled() const;
void setPreset(Preset preset);
void setSampleRate(uInt32 sampleRate);
void setFragmentSize(uInt32 fragmentSize);
void setBufferSize(uInt32 bufferSize);
void setHeadroom(uInt32 headroom);
void setResamplingQuality(ResamplingQuality resamplingQuality);
void setVolume(uInt32 volume);
void setEnabled(bool isEnabled);
void setPersistent(bool isPersistent);
private:
bool customSettings() const;
void updatePresetFromSettings();
private:
Settings* mySettings;
Preset myPreset;
uInt32 myPresetSampleRate;
uInt32 myPresetFragmentSize;
uInt32 myPresetBufferSize;
uInt32 myPresetHeadroom;
ResamplingQuality myPresetResamplingQuality;
bool myIsPersistent;
};
#endif // AUDIO_PARAMTERS_HXX

View File

@ -144,10 +144,6 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
// Always recreate renderer (some systems need this)
if(myRenderer)
{
// Always clear the (double-buffered) renderer surface
SDL_RenderClear(myRenderer);
SDL_RenderPresent(myRenderer);
SDL_RenderClear(myRenderer);
SDL_DestroyRenderer(myRenderer);
myRenderer = nullptr;
}
@ -234,6 +230,12 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
myOSystem.logMessage(msg, 0);
return false;
}
// Always clear the (double-buffered) renderer surface
SDL_RenderClear(myRenderer);
SDL_RenderPresent(myRenderer);
SDL_RenderClear(myRenderer);
SDL_RendererInfo renderinfo;
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
myOSystem.settings().setValue("video", renderinfo.name);
@ -241,6 +243,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::setTitle(const string& title)
{
myScreenTitle = title;

View File

@ -50,6 +50,8 @@
#include "SoundNull.hxx"
#endif
class AudioSettings;
/**
This class deals with the different framebuffer/sound/event
implementations for the various ports of Stella, and always returns a
@ -108,10 +110,10 @@ class MediaFactory
return make_unique<FrameBufferSDL2>(osystem);
}
static unique_ptr<Sound> createAudio(OSystem& osystem)
static unique_ptr<Sound> createAudio(OSystem& osystem, AudioSettings& audioSettings)
{
#ifdef SOUND_SUPPORT
return make_unique<SoundSDL2>(osystem);
return make_unique<SoundSDL2>(osystem, audioSettings);
#else
return make_unique<SoundNull>(osystem);
#endif

View File

@ -21,6 +21,8 @@
#include "bspf.hxx"
#include "Sound.hxx"
#include "OSystem.hxx"
#include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
/**
This class implements a Null sound object, where-by sound generation
@ -53,26 +55,11 @@ class SoundNull : public Sound
*/
void setEnabled(bool state) override { }
/**
Sets the number of channels (mono or stereo sound).
@param channels The number of channels
*/
void setChannels(uInt32 channels) override { }
/**
Sets the display framerate. Sound generation for NTSC and PAL games
depends on the framerate, so we need to set it here.
@param framerate The base framerate depending on NTSC or PAL ROM
*/
void setFrameRate(float framerate) override { }
/**
Initializes the sound device. This must be called before any
calls are made to derived methods.
*/
void open() override { }
void open(shared_ptr<AudioQueue>, EmulationTiming*) override { }
/**
Should be called to close the sound device. Once called the sound
@ -92,15 +79,6 @@ class SoundNull : public Sound
*/
void reset() override { }
/**
Sets the sound register to a given value.
@param addr The register address
@param value The value to save into the register
@param cycle The system cycle at which the register is being updated
*/
void set(uInt16 addr, uInt8 value, uInt64 cycle) override { }
/**
Sets the volume of the sound device to the specified level. The
volume is given as a percentage from 0 to 100. Values outside
@ -108,7 +86,7 @@ class SoundNull : public Sound
@param percent The new volume percentage level for the sound device
*/
void setVolume(Int32 percent) override { }
void setVolume(uInt32 percent) override { }
/**
Adjusts the volume of the sound device based on the given direction.
@ -118,54 +96,6 @@ class SoundNull : public Sound
*/
void adjustVolume(Int8 direction) override { }
public:
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
bool save(Serializer& out) const override
{
out.putString("TIASound");
for(int i = 0; i < 6; ++i)
out.putByte(0);
// myLastRegisterSetCycle
out.putInt(0);
return true;
}
/**
Loads the current state of this device from the given Serializer.
@param in The Serializer device to load from.
@return The result of the load. True on success, false on failure.
*/
bool load(Serializer& in) override
{
if(in.getString() != "TIASound")
return false;
// Read sound registers and discard
for(int i = 0; i < 6; ++i)
in.getByte();
// myLastRegisterSetCycle
in.getInt();
return true;
}
/**
Get a descriptor for this console class (used in error checking).
@return The name of the object
*/
string name() const override { return "TIASound"; }
private:
// Following constructors and assignment operators not supported
SoundNull() = delete;

View File

@ -22,46 +22,38 @@
#include <cmath>
#include "SDL_lib.hxx"
#include "TIASnd.hxx"
#include "TIAConstants.hxx"
#include "FrameBuffer.hxx"
#include "Settings.hxx"
#include "System.hxx"
#include "OSystem.hxx"
#include "Console.hxx"
#include "SoundSDL2.hxx"
#include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
#include "AudioSettings.hxx"
#include "audio/SimpleResampler.hxx"
#include "audio/LanczosResampler.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::SoundSDL2(OSystem& osystem)
SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings)
: Sound(osystem),
myIsEnabled(false),
myIsInitializedFlag(false),
myLastRegisterSetCycle(0),
myNumChannels(0),
myFragmentSizeLogBase2(0),
myFragmentSizeLogDiv1(0),
myFragmentSizeLogDiv2(0),
myIsMuted(true),
myVolume(100)
myVolume(100),
myVolumeFactor(0xffff),
myCurrentFragment(nullptr),
myAudioSettings(audioSettings)
{
myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2);
#ifdef BSPF_WINDOWS
// TODO - remove the following code once we convert to the new sound
// core, and use 32-bit floating point samples and do
// our own resampling
SDL_setenv("SDL_AUDIODRIVER", "directsound", true);
#endif
// The sound system is opened only once per program run, to eliminate
// issues with opening and closing it multiple times
// This fixes a bug most prevalent with ATI video cards in Windows,
// whereby sound stopped working after the first video change
SDL_AudioSpec desired;
desired.freq = myOSystem.settings().getInt("freq");
desired.format = AUDIO_S16SYS;
desired.freq = myAudioSettings.sampleRate();
desired.format = AUDIO_F32SYS;
desired.channels = 2;
desired.samples = myOSystem.settings().getInt("fragsize");
desired.samples = myAudioSettings.fragmentSize();
desired.callback = callback;
desired.userdata = static_cast<void*>(this);
@ -87,13 +79,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
return;
}
// Pre-compute fragment-related variables as much as possible
myFragmentSizeLogBase2 = log(myHardwareSpec.samples) / log(2.0);
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / 60.0;
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / 60.0;
myIsInitializedFlag = true;
SDL_PauseAudio(1);
mute(true);
myOSystem.logMessage("SoundSDL2::SoundSDL2 initialized", 2);
}
@ -101,43 +89,43 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::~SoundSDL2()
{
// Close the SDL audio system if it's initialized
if(myIsInitializedFlag)
{
SDL_CloseAudio();
myIsEnabled = myIsInitializedFlag = false;
}
if (!myIsInitializedFlag) return;
SDL_CloseAudio();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::setEnabled(bool state)
{
myOSystem.settings().setValue("sound", state);
myAudioSettings.setEnabled(state);
if (myAudioQueue) myAudioQueue->ignoreOverflows(!state);
myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" :
"SoundSDL2::setEnabled(false)", 2);
"SoundSDL2::setEnabled(false)", 2);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::open()
void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
EmulationTiming* emulationTiming)
{
myEmulationTiming = emulationTiming;
myOSystem.logMessage("SoundSDL2::open started ...", 2);
myIsEnabled = false;
mute(true);
if(!myIsInitializedFlag || !myOSystem.settings().getBool("sound"))
audioQueue->ignoreOverflows(!myAudioSettings.enabled());
if(!myAudioSettings.enabled())
{
myOSystem.logMessage("Sound disabled\n", 1);
return;
}
// Now initialize the TIASound object which will actually generate sound
myTIASound.outputFrequency(myHardwareSpec.freq);
const string& chanResult =
myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2);
myAudioQueue = audioQueue;
myUnderrun = true;
myCurrentFragment = nullptr;
// Adjust volume to that defined in settings
myVolume = myOSystem.settings().getInt("volume");
setVolume(myVolume);
setVolume(myAudioSettings.volume());
// Show some info
ostringstream buf;
@ -146,12 +134,12 @@ void SoundSDL2::open()
<< " Frag size: " << uInt32(myHardwareSpec.samples) << endl
<< " Frequency: " << uInt32(myHardwareSpec.freq) << endl
<< " Channels: " << uInt32(myHardwareSpec.channels)
<< " (" << chanResult << ")" << endl
<< endl;
myOSystem.logMessage(buf.str(), 1);
initResampler();
// And start the SDL sound subsystem ...
myIsEnabled = true;
mute(false);
myOSystem.logMessage("SoundSDL2::open finished", 2);
@ -160,15 +148,16 @@ void SoundSDL2::open()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::close()
{
if(myIsInitializedFlag)
{
myIsEnabled = false;
SDL_PauseAudio(1);
myLastRegisterSetCycle = 0;
myTIASound.reset();
myRegWriteQueue.clear();
myOSystem.logMessage("SoundSDL2::close", 2);
}
if(!myIsInitializedFlag) return;
mute(true);
if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment);
myAudioQueue.reset();
myCurrentFragment = nullptr;
myOSystem.logMessage("SoundSDL2::close", 2);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -176,33 +165,25 @@ void SoundSDL2::mute(bool state)
{
if(myIsInitializedFlag)
{
myIsMuted = state;
SDL_PauseAudio(myIsMuted ? 1 : 0);
SDL_PauseAudio(state ? 1 : 0);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::reset()
{
if(myIsInitializedFlag)
{
SDL_PauseAudio(1);
myLastRegisterSetCycle = 0;
myTIASound.reset();
myRegWriteQueue.clear();
mute(myIsMuted);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::setVolume(Int32 percent)
void SoundSDL2::setVolume(uInt32 percent)
{
if(myIsInitializedFlag && (percent >= 0) && (percent <= 100))
if(myIsInitializedFlag && (percent <= 100))
{
myOSystem.settings().setValue("volume", percent);
SDL_LockAudio();
myAudioSettings.setVolume(percent);
myVolume = percent;
myTIASound.volume(percent);
SDL_LockAudio();
myVolumeFactor = std::pow(static_cast<float>(percent) / 100.f, 2.f);
SDL_UnlockAudio();
}
}
@ -234,285 +215,80 @@ void SoundSDL2::adjustVolume(Int8 direction)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::setChannels(uInt32 channels)
uInt32 SoundSDL2::getFragmentSize() const
{
if(channels == 1 || channels == 2)
myNumChannels = channels;
return myHardwareSpec.samples;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::setFrameRate(float framerate)
uInt32 SoundSDL2::getSampleRate() const
{
// Recalculate since frame rate has changed
// FIXME - should we clear out the queue or adjust the values in it?
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / framerate;
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / framerate;
return myHardwareSpec.freq;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::set(uInt16 addr, uInt8 value, uInt64 cycle)
void SoundSDL2::processFragment(float* stream, uInt32 length)
{
SDL_LockAudio();
myResampler->fillFragment(stream, length);
// First, calculate how many seconds would have past since the last
// register write on a real 2600
double delta = double(cycle - myLastRegisterSetCycle) / 1193191.66666667;
// Now, adjust the time based on the frame rate the user has selected. For
// the sound to "scale" correctly, we have to know the games real frame
// rate (e.g., 50 or 60) and the currently emulated frame rate. We use these
// values to "scale" the time before the register change occurs.
myRegWriteQueue.enqueue(addr, value, delta);
// Update last cycle counter to the current cycle
myLastRegisterSetCycle = cycle;
SDL_UnlockAudio();
for (uInt32 i = 0; i < length; i++) stream[i] = stream[i] * myVolumeFactor;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::processFragment(Int16* stream, uInt32 length)
void SoundSDL2::initResampler()
{
uInt32 channels = myHardwareSpec.channels;
length = length / channels;
Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
Int16* nextFragment = nullptr;
// If there are excessive items on the queue then we'll remove some
if(myRegWriteQueue.duration() > myFragmentSizeLogDiv1)
{
double removed = 0.0;
while(removed < myFragmentSizeLogDiv2)
{
RegWrite& info = myRegWriteQueue.front();
removed += info.delta;
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
}
double position = 0.0;
double remaining = length;
while(remaining > 0.0)
{
if(myRegWriteQueue.size() == 0)
{
// There are no more pending TIA sound register updates so we'll
// use the current settings to finish filling the sound fragment
myTIASound.process(stream + (uInt32(position) * channels),
length - uInt32(position));
// Since we had to fill the fragment we'll reset the cycle counter
// to zero. NOTE: This isn't 100% correct, however, it'll do for
// now. We should really remember the overrun and remove it from
// the delta of the next write.
myLastRegisterSetCycle = 0;
break;
}
if (myUnderrun)
nextFragment = myAudioQueue->size() > myEmulationTiming->prebufferFragmentCount() ?
myAudioQueue->dequeue(myCurrentFragment) : nullptr;
else
{
// There are pending TIA sound register updates so we need to
// update the sound buffer to the point of the next register update
RegWrite& info = myRegWriteQueue.front();
nextFragment = myAudioQueue->dequeue(myCurrentFragment);
// How long will the remaining samples in the fragment take to play
double duration = remaining / myHardwareSpec.freq;
myUnderrun = nextFragment == nullptr;
if (nextFragment) myCurrentFragment = nextFragment;
// Does the register update occur before the end of the fragment?
if(info.delta <= duration)
{
// If the register update time hasn't already passed then
// process samples upto the point where it should occur
if(info.delta > 0.0)
{
// Process the fragment upto the next TIA register write. We
// round the count passed to process up if needed.
double samples = (myHardwareSpec.freq * info.delta);
myTIASound.process(stream + (uInt32(position) * channels),
uInt32(samples) + uInt32(position + samples) -
(uInt32(position) + uInt32(samples)));
return nextFragment;
};
position += samples;
remaining -= samples;
}
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
else
{
// The next register update occurs in the next fragment so finish
// this fragment with the current TIA settings and reduce the register
// update delay by the corresponding amount of time
myTIASound.process(stream + (uInt32(position) * channels),
length - uInt32(position));
info.delta -= duration;
break;
}
}
Resampler::Format formatFrom =
Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo());
Resampler::Format formatTo =
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1);
switch (myAudioSettings.resamplingQuality()) {
case AudioSettings::ResamplingQuality::nearestNeightbour:
myResampler = make_unique<SimpleResampler>(formatFrom, formatTo, nextFragmentCallback);
(cerr << "resampling quality 1: using nearest neighbor resampling\n").flush();
break;
case AudioSettings::ResamplingQuality::lanczos_2:
(cerr << "resampling quality 2: using nearest Lanczos resampling, a = 2\n").flush();
myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 2);
break;
case AudioSettings::ResamplingQuality::lanczos_3:
(cerr << "resampling quality 3: using nearest Lanczos resampling, a = 3\n").flush();
myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 3);
break;
default:
throw runtime_error("invalid resampling quality");
break;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::callback(void* udata, uInt8* stream, int len)
{
SoundSDL2* sound = static_cast<SoundSDL2*>(udata);
if(sound->myIsEnabled)
{
// The callback is requesting 8-bit (unsigned) data, but the TIA sound
// emulator deals in 16-bit (signed) data
// So, we need to convert the pointer and half the length
sound->processFragment(reinterpret_cast<Int16*>(stream), uInt32(len) >> 1);
}
SoundSDL2* self = static_cast<SoundSDL2*>(udata);
if (self->myAudioQueue)
self->processFragment(reinterpret_cast<float*>(stream), len >> 2);
else
SDL_memset(stream, 0, len); // Write 'silence'
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundSDL2::save(Serializer& out) const
{
try
{
out.putString(name());
// Only get the TIA sound registers if sound is enabled
if(myIsInitializedFlag)
{
out.putByte(myTIASound.get(TIARegister::AUDC0));
out.putByte(myTIASound.get(TIARegister::AUDC1));
out.putByte(myTIASound.get(TIARegister::AUDF0));
out.putByte(myTIASound.get(TIARegister::AUDF1));
out.putByte(myTIASound.get(TIARegister::AUDV0));
out.putByte(myTIASound.get(TIARegister::AUDV1));
}
else
for(int i = 0; i < 6; ++i)
out.putByte(0);
out.putLong(myLastRegisterSetCycle);
}
catch(...)
{
myOSystem.logMessage("ERROR: SoundSDL2::save", 0);
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundSDL2::load(Serializer& in)
{
try
{
if(in.getString() != name())
return false;
// Only update the TIA sound registers if sound is enabled
// Make sure to empty the queue of previous sound fragments
if(myIsInitializedFlag)
{
SDL_PauseAudio(1);
myRegWriteQueue.clear();
myTIASound.set(TIARegister::AUDC0, in.getByte());
myTIASound.set(TIARegister::AUDC1, in.getByte());
myTIASound.set(TIARegister::AUDF0, in.getByte());
myTIASound.set(TIARegister::AUDF1, in.getByte());
myTIASound.set(TIARegister::AUDV0, in.getByte());
myTIASound.set(TIARegister::AUDV1, in.getByte());
if(!myIsMuted) SDL_PauseAudio(0);
}
else
for(int i = 0; i < 6; ++i)
in.getByte();
myLastRegisterSetCycle = in.getLong();
}
catch(...)
{
myOSystem.logMessage("ERROR: SoundSDL2::load", 0);
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::RegWriteQueue::RegWriteQueue(uInt32 capacity)
: myBuffer(make_unique<RegWrite[]>(capacity)),
myCapacity(capacity),
mySize(0),
myHead(0),
myTail(0)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::RegWriteQueue::clear()
{
myHead = myTail = mySize = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::RegWriteQueue::dequeue()
{
if(mySize > 0)
{
myHead = (myHead + 1) % myCapacity;
--mySize;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
double SoundSDL2::RegWriteQueue::duration() const
{
double duration = 0.0;
for(uInt32 i = 0; i < mySize; ++i)
{
duration += myBuffer[(myHead + i) % myCapacity].delta;
}
return duration;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::RegWriteQueue::enqueue(uInt16 addr, uInt8 value, double delta)
{
// If an attempt is made to enqueue more than the queue can hold then
// we'll enlarge the queue's capacity.
if(mySize == myCapacity)
grow();
RegWrite& reg = myBuffer[myTail];
reg.addr = addr;
reg.value = value;
reg.delta = delta;
myTail = (myTail + 1) % myCapacity;
++mySize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::RegWrite& SoundSDL2::RegWriteQueue::front() const
{
assert(mySize != 0);
return myBuffer[myHead];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundSDL2::RegWriteQueue::size() const
{
return mySize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::RegWriteQueue::grow()
{
unique_ptr<RegWrite[]> buffer = make_unique<RegWrite[]>(myCapacity*2);
for(uInt32 i = 0; i < mySize; ++i)
buffer[i] = myBuffer[(myHead + i) % myCapacity];
myHead = 0;
myTail = mySize;
myCapacity *= 2;
myBuffer = std::move(buffer);
SDL_memset(stream, 0, len);
}
#endif // SOUND_SUPPORT

View File

@ -21,17 +21,20 @@
#define SOUND_SDL2_HXX
class OSystem;
class AudioQueue;
class EmulationTiming;
class AudioSettings;
#include "SDL_lib.hxx"
#include "bspf.hxx"
#include "TIASnd.hxx"
#include "Sound.hxx"
#include "audio/Resampler.hxx"
/**
This class implements the sound API for SDL.
@author Stephen Anthony and Bradford W. Mott
@author Stephen Anthony and Christian Speckner (DirtyHairy)
*/
class SoundSDL2 : public Sound
{
@ -40,7 +43,7 @@ class SoundSDL2 : public Sound
Create a new sound object. The init method must be invoked before
using the object.
*/
SoundSDL2(OSystem& osystem);
SoundSDL2(OSystem& osystem, AudioSettings& audioSettings);
/**
Destructor
@ -51,34 +54,15 @@ class SoundSDL2 : public Sound
/**
Enables/disables the sound subsystem.
@param state True or false, to enable or disable the sound system
@param enable Either true or false, to enable or disable the sound system
*/
void setEnabled(bool state) override;
/**
Sets the number of channels (mono or stereo sound). Note that this
determines how the emulation should 'mix' the channels of the TIA sound
system (of which there are always two). It does not specify the actual
number of hardware channels that SDL should use; it will always attempt
to use two channels in hardware.
@param channels The number of channels
*/
void setChannels(uInt32 channels) override;
/**
Sets the display framerate. Sound generation for NTSC and PAL games
depends on the framerate, so we need to set it here.
@param framerate The base framerate depending on NTSC or PAL ROM
*/
void setFrameRate(float framerate) override;
void setEnabled(bool enable) override;
/**
Initializes the sound device. This must be called before any
calls are made to derived methods.
*/
void open() override;
void open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulationTiming) override;
/**
Should be called to close the sound device. Once called the sound
@ -98,15 +82,6 @@ class SoundSDL2 : public Sound
*/
void reset() override;
/**
Sets the sound register to a given value.
@param addr The register address
@param value The value to save into the register
@param cycle The system cycle at which the register is being updated
*/
void set(uInt16 addr, uInt8 value, uInt64 cycle) override;
/**
Sets the volume of the sound device to the specified level. The
volume is given as a percentage from 0 to 100. Values outside
@ -114,7 +89,7 @@ class SoundSDL2 : public Sound
@param percent The new volume percentage level for the sound device
*/
void setVolume(Int32 percent) override;
void setVolume(uInt32 percent) override;
/**
Adjusts the volume of the sound device based on the given direction.
@ -124,29 +99,9 @@ class SoundSDL2 : public Sound
*/
void adjustVolume(Int8 direction) override;
public:
/**
Saves the current state of this device to the given Serializer.
uInt32 getFragmentSize() const override;
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
bool save(Serializer& out) const override;
/**
Loads the current state of this device from the given Serializer.
@param in The Serializer device to load from.
@return The result of the load. True on success, false on failure.
*/
bool load(Serializer& in) override;
/**
Get a descriptor for this console class (used in error checking).
@return The name of the object
*/
string name() const override { return "TIASound"; }
uInt32 getSampleRate() const override;
protected:
/**
@ -157,124 +112,33 @@ class SoundSDL2 : public Sound
@param stream Pointer to the start of the fragment
@param length Length of the fragment
*/
void processFragment(Int16* stream, uInt32 length);
protected:
// Struct to hold information regarding a TIA sound register write
struct RegWrite
{
uInt16 addr;
uInt8 value;
double delta;
RegWrite(uInt16 a = 0, uInt8 v = 0, double d = 0.0)
: addr(a), value(v), delta(d) { }
};
/**
A queue class used to hold TIA sound register writes before being
processed while creating a sound fragment.
*/
class RegWriteQueue
{
public:
/**
Create a new queue instance with the specified initial
capacity. If the queue ever reaches its capacity then it will
automatically increase its size.
*/
RegWriteQueue(uInt32 capacity = 512);
public:
/**
Clear any items stored in the queue.
*/
void clear();
/**
Dequeue the first object in the queue.
*/
void dequeue();
/**
Return the duration of all the items in the queue.
*/
double duration() const;
/**
Enqueue the specified object.
*/
void enqueue(uInt16 addr, uInt8 value, double delta);
/**
Return the item at the front on the queue.
@return The item at the front of the queue.
*/
RegWrite& front() const;
/**
Answers the number of items currently in the queue.
@return The number of items in the queue.
*/
uInt32 size() const;
private:
// Increase the size of the queue
void grow();
private:
unique_ptr<RegWrite[]> myBuffer;
uInt32 myCapacity;
uInt32 mySize;
uInt32 myHead;
uInt32 myTail;
private:
// Following constructors and assignment operators not supported
RegWriteQueue(const RegWriteQueue&) = delete;
RegWriteQueue(RegWriteQueue&&) = delete;
RegWriteQueue& operator=(const RegWriteQueue&) = delete;
RegWriteQueue& operator=(RegWriteQueue&&) = delete;
};
void processFragment(float* stream, uInt32 length);
private:
// TIASound emulation object
TIASound myTIASound;
// Indicates if the sound subsystem is to be initialized
bool myIsEnabled;
void initResampler();
private:
// Indicates if the sound device was successfully initialized
bool myIsInitializedFlag;
// Indicates the cycle when a sound register was last set
uInt64 myLastRegisterSetCycle;
// Indicates the number of channels (mono or stereo)
uInt32 myNumChannels;
// Log base 2 of the selected fragment size
double myFragmentSizeLogBase2;
// The myFragmentSizeLogBase2 variable is used in only two places,
// both of which involve an expensive division in the sound
// processing callback
// These are pre-computed to speed up the callback as much as possible
double myFragmentSizeLogDiv1, myFragmentSizeLogDiv2;
// Indicates if the sound is currently muted
bool myIsMuted;
// Current volume as a percentage (0 - 100)
uInt32 myVolume;
float myVolumeFactor;
// Audio specification structure
SDL_AudioSpec myHardwareSpec;
// Queue of TIA register writes
RegWriteQueue myRegWriteQueue;
shared_ptr<AudioQueue> myAudioQueue;
EmulationTiming* myEmulationTiming;
Int16* myCurrentFragment;
bool myUnderrun;
unique_ptr<Resampler> myResampler;
AudioSettings& myAudioSettings;
private:
// Callback function invoked by the SDL Audio library when it needs data

View File

@ -18,7 +18,7 @@
#ifndef VERSION_HXX
#define VERSION_HXX
#define STELLA_VERSION "5.2_pre"
#define STELLA_VERSION "5.2_soundtest-1"
#define STELLA_BUILD "4138"
#endif

View File

@ -0,0 +1,46 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "ConvolutionBuffer.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ConvolutionBuffer::ConvolutionBuffer(uInt32 size)
: myFirstIndex(0),
mySize(size)
{
myData = make_unique<float[]>(mySize);
memset(myData.get(), 0, mySize * sizeof(float));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ConvolutionBuffer::shift(float nextValue)
{
myData[myFirstIndex] = nextValue;
myFirstIndex = (myFirstIndex + 1) % mySize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float ConvolutionBuffer::convoluteWith(float* kernel) const
{
float result = 0.;
for (uInt32 i = 0; i < mySize; i++) {
result += kernel[i] * myData[(myFirstIndex + i) % mySize];
}
return result;
}

View File

@ -0,0 +1,51 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef CONVOLUTION_BUFFER_HXX
#define CONVOLUTION_BUFFER_HXX
#include "bspf.hxx"
class ConvolutionBuffer
{
public:
ConvolutionBuffer(uInt32 size);
void shift(float nextValue);
float convoluteWith(float* kernel) const;
private:
unique_ptr<float[]> myData;
uInt32 myFirstIndex;
uInt32 mySize;
private:
ConvolutionBuffer() = delete;
ConvolutionBuffer(const ConvolutionBuffer&) = delete;
ConvolutionBuffer(ConvolutionBuffer&&) = delete;
ConvolutionBuffer& operator=(const ConvolutionBuffer&) = delete;
ConvolutionBuffer& operator=(ConvolutionBuffer&&) = delete;
};
#endif // CONVOLUTION_BUFFER_HXX

View File

@ -0,0 +1,208 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif
#include "LanczosResampler.hxx"
namespace {
constexpr float CLIPPING_FACTOR = 0.75;
uInt32 reducedDenominator(uInt32 n, uInt32 d)
{
for (uInt32 i = std::min(n ,d); i > 1; i--) {
if ((n % i == 0) && (d % i == 0)) {
n /= i;
d /= i;
i = std::min(n ,d);
}
}
return d;
}
float sinc(float x)
{
// We calculate the sinc with double precision in order to compensate for precision loss
// around zero
return x == 0.f ? 1 : static_cast<float>(
sin(M_PI * static_cast<double>(x)) / M_PI / static_cast<double>(x)
);
}
double lanczosKernel(float x, uInt32 a) {
return sinc(x) * sinc(x / static_cast<float>(a));
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LanczosResampler::LanczosResampler(
Resampler::Format formatFrom,
Resampler::Format formatTo,
Resampler::NextFragmentCallback nextFragmentCallback,
uInt32 kernelParameter)
:
Resampler(formatFrom, formatTo, nextFragmentCallback),
// In order to find the number of kernels we need to precompute, we need to find N minimal such that
//
// N / formatTo.sampleRate = M / formatFrom.sampleRate
//
// with integral N and M. Equivalently, we have
//
// formatFrom.sampleRate / formatTo.sampleRate = M / N
//
// -> we find N from fully reducing the fraction.
myPrecomputedKernelCount(reducedDenominator(formatFrom.sampleRate, formatTo.sampleRate)),
myKernelSize(2 * kernelParameter),
myCurrentKernelIndex(0),
myKernelParameter(kernelParameter),
myCurrentFragment(nullptr),
myFragmentIndex(0),
myIsUnderrun(true),
myTimeIndex(0)
{
myPrecomputedKernels = make_unique<float[]>(myPrecomputedKernelCount * myKernelSize);
if (myFormatFrom.stereo)
{
myBufferL = make_unique<ConvolutionBuffer>(myKernelSize);
myBufferR = make_unique<ConvolutionBuffer>(myKernelSize);
}
else
myBuffer = make_unique<ConvolutionBuffer>(myKernelSize);
precomputeKernels();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LanczosResampler::precomputeKernels()
{
// timeIndex = time * formatFrom.sampleRate * formatTo.sampleRAte
uInt32 timeIndex = 0;
for (uInt32 i = 0; i < myPrecomputedKernelCount; i++) {
float* kernel = myPrecomputedKernels.get() + myKernelSize * i;
// The kernel is normalized such to be evaluate on time * formatFrom.sampleRate
float center =
static_cast<float>(timeIndex) / static_cast<float>(myFormatTo.sampleRate);
for (uInt32 j = 0; j < 2 * myKernelParameter; j++) {
kernel[j] = lanczosKernel(
center - static_cast<float>(j) + static_cast<float>(myKernelParameter) - 1.f, myKernelParameter
) * CLIPPING_FACTOR;
}
// Next step: time += 1 / formatTo.sampleRate
//
// By construction, we limit the argument during kernel evaluation to 0 .. 1, which
// corresponds to 0 .. 1 / formatFrom.sampleRate for time. To implement this, we decompose
// time as follows:
//
// time = N / formatFrom.sampleRate + delta
// timeIndex = N * formatTo.sampleRate + delta * formatTo.sampleRate * formatFrom.sampleRate
//
// with N integral and delta < 0. From this, it follows that we replace
// time with delta, i.e. take the modulus of timeIndex.
timeIndex = (timeIndex + myFormatFrom.sampleRate) % myFormatTo.sampleRate;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LanczosResampler::fillFragment(float* fragment, uInt32 length)
{
if (myIsUnderrun) {
Int16* nextFragment = myNextFragmentCallback();
if (nextFragment) {
myCurrentFragment = nextFragment;
myFragmentIndex = 0;
myIsUnderrun = false;
}
}
if (!myCurrentFragment) {
memset(fragment, 0, sizeof(float) * length);
return;
}
const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length;
for (uInt32 i = 0; i < outputSamples; i++) {
float* kernel = myPrecomputedKernels.get() + (myCurrentKernelIndex * myKernelSize);
myCurrentKernelIndex = (myCurrentKernelIndex + 1) % myPrecomputedKernelCount;
if (myFormatFrom.stereo) {
float sampleL = myBufferL->convoluteWith(kernel);
float sampleR = myBufferR->convoluteWith(kernel);
if (myFormatTo.stereo) {
fragment[2*i] = sampleL;
fragment[2*i + 1] = sampleR;
}
else
fragment[i] = (sampleL + sampleR) / 2.f;
} else {
float sample = myBuffer->convoluteWith(kernel);
if (myFormatTo.stereo)
fragment[2*i] = fragment[2*i + 1] = sample;
else
fragment[i] = sample;
}
myTimeIndex += myFormatFrom.sampleRate;
uInt32 samplesToShift = myTimeIndex / myFormatTo.sampleRate;
if (samplesToShift == 0) continue;
myTimeIndex %= myFormatTo.sampleRate;
shiftSamples(samplesToShift);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline void LanczosResampler::shiftSamples(uInt32 samplesToShift)
{
while (samplesToShift-- > 0) {
if (myFormatFrom.stereo) {
myBufferL->shift(myCurrentFragment[2*myFragmentIndex] / static_cast<float>(0x7fff));
myBufferR->shift(myCurrentFragment[2*myFragmentIndex + 1] / static_cast<float>(0x7fff));
}
else
myBuffer->shift(myCurrentFragment[myFragmentIndex] / static_cast<float>(0x7fff));
myFragmentIndex++;
if (myFragmentIndex >= myFormatFrom.fragmentSize) {
myFragmentIndex %= myFormatFrom.fragmentSize;
Int16* nextFragment = myNextFragmentCallback();
if (nextFragment) {
myCurrentFragment = nextFragment;
myIsUnderrun = false;
} else {
(cerr << "audio buffer underrun\n").flush();
myIsUnderrun = true;
}
}
}
}

View File

@ -0,0 +1,65 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef LANCZOS_RESAMPLER_HXX
#define LANCZOS_RESAMPLER_HXX
#include "bspf.hxx"
#include "Resampler.hxx"
#include "ConvolutionBuffer.hxx"
class LanczosResampler : public Resampler
{
public:
LanczosResampler(
Resampler::Format formatFrom,
Resampler::Format formatTo,
Resampler::NextFragmentCallback nextFragmentCallback,
uInt32 kernelParameter
);
virtual void fillFragment(float* fragment, uInt32 length);
virtual ~LanczosResampler() = default;
private:
void precomputeKernels();
void shiftSamples(uInt32 samplesToShift);
private:
uInt32 myPrecomputedKernelCount;
uInt32 myKernelSize;
uInt32 myCurrentKernelIndex;
unique_ptr<float[]> myPrecomputedKernels;
uInt32 myKernelParameter;
unique_ptr<ConvolutionBuffer> myBuffer;
unique_ptr<ConvolutionBuffer> myBufferL;
unique_ptr<ConvolutionBuffer> myBufferR;
Int16* myCurrentFragment;
uInt32 myFragmentIndex;
bool myIsUnderrun;
uInt32 myTimeIndex;
};
#endif // LANCZOS_RESAMPLER_HXX

View File

@ -0,0 +1,79 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef RESAMPLER_HXX
#define RESAMPLER_HXX
#include <functional>
#include "bspf.hxx"
class Resampler {
public:
using NextFragmentCallback = std::function<Int16*()>;
class Format {
public:
Format(uInt32 f_sampleRate, uInt32 f_fragmentSize, bool f_stereo) :
sampleRate(f_sampleRate),
fragmentSize(f_fragmentSize),
stereo(f_stereo)
{}
public:
uInt32 sampleRate;
uInt32 fragmentSize;
bool stereo;
private:
Format() = delete;
};
public:
Resampler(Format formatFrom, Format formatTo, NextFragmentCallback nextFragmentCallback) :
myFormatFrom(formatFrom),
myFormatTo(formatTo),
myNextFragmentCallback(nextFragmentCallback)
{}
virtual void fillFragment(float* fragment, uInt32 length) = 0;
virtual ~Resampler() {}
protected:
Format myFormatFrom;
Format myFormatTo;
NextFragmentCallback myNextFragmentCallback;
private:
Resampler() = delete;
Resampler(const Resampler&) = delete;
Resampler(Resampler&&) = delete;
Resampler& operator=(const Resampler&) = delete;
Resampler& operator=(Resampler&&) = delete;
};
#endif // RESAMPLER_HXX

View File

@ -0,0 +1,96 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "SimpleResampler.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SimpleResampler::SimpleResampler(
Resampler::Format formatFrom,
Resampler::Format formatTo,
Resampler::NextFragmentCallback nextFragmentCallback)
: Resampler(formatFrom, formatTo, nextFragmentCallback),
myCurrentFragment(nullptr),
myTimeIndex(0),
myFragmentIndex(0),
myIsUnderrun(true)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SimpleResampler::fillFragment(float* fragment, uInt32 length)
{
if (myIsUnderrun) {
Int16* nextFragment = myNextFragmentCallback();
if (nextFragment) {
myCurrentFragment = nextFragment;
myFragmentIndex = 0;
myIsUnderrun = false;
}
}
if (!myCurrentFragment) {
memset(fragment, 0, sizeof(float) * length);
return;
}
const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length;
// For the following math, remember that myTimeIndex = time * myFormatFrom.sampleRate * myFormatTo.sampleRate
for (uInt32 i = 0; i < outputSamples; i++) {
if (myFormatFrom.stereo) {
float sampleL = static_cast<float>(myCurrentFragment[2*myFragmentIndex]) / static_cast<float>(0x7fff);
float sampleR = static_cast<float>(myCurrentFragment[2*myFragmentIndex + 1]) / static_cast<float>(0x7fff);
if (myFormatTo.stereo) {
fragment[2*i] = sampleL;
fragment[2*i + 1] = sampleR;
}
else
fragment[i] = (sampleL + sampleR) / 2.f;
} else {
float sample = static_cast<float>(myCurrentFragment[myFragmentIndex] / static_cast<float>(0x7fff));
if (myFormatTo.stereo)
fragment[2*i] = fragment[2*i + 1] = sample;
else
fragment[i] = sample;
}
// time += 1 / myFormatTo.sampleRate
myTimeIndex += myFormatFrom.sampleRate;
// time >= 1 / myFormatFrom.sampleRate
if (myTimeIndex >= myFormatTo.sampleRate) {
// myFragmentIndex += time * myFormatFrom.sampleRate
myFragmentIndex += myTimeIndex / myFormatTo.sampleRate;
myTimeIndex %= myFormatTo.sampleRate;
}
if (myFragmentIndex >= myFormatFrom.fragmentSize) {
myFragmentIndex %= myFormatFrom.fragmentSize;
Int16* nextFragment = myNextFragmentCallback();
if (nextFragment)
myCurrentFragment = nextFragment;
else {
(cerr << "audio buffer underrun\n").flush();
myIsUnderrun = true;
}
}
}
}

View File

@ -0,0 +1,52 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef SIMPLE_RESAMPLER_HXX
#define SIMPLE_RESAMPLER_HXX
#include "bspf.hxx"
#include "Resampler.hxx"
class SimpleResampler : public Resampler
{
public:
SimpleResampler(
Resampler::Format formatFrom,
Resampler::Format formatTo,
Resampler::NextFragmentCallback NextFragmentCallback
);
virtual void fillFragment(float* fragment, uInt32 length);
private:
Int16* myCurrentFragment;
uInt32 myTimeIndex;
uInt32 myFragmentIndex;
bool myIsUnderrun;
private:
SimpleResampler() = delete;
SimpleResampler(const SimpleResampler&) = delete;
SimpleResampler(SimpleResampler&&) = delete;
SimpleResampler& operator=(const SimpleResampler&) = delete;
SimpleResampler& operator=(const SimpleResampler&&) = delete;
};
#endif // SIMPLE_RESAMPLER_HXX

View File

@ -0,0 +1,12 @@
MODULE := src/common/audio
MODULE_OBJS := \
src/common/audio/SimpleResampler.o \
src/common/audio/ConvolutionBuffer.o \
src/common/audio/LanczosResampler.o
MODULE_DIRS += \
src/emucore/tia
# Include common rules
include $(srcdir)/common.rules

View File

@ -15,7 +15,9 @@ MODULE_OBJS := \
src/common/RewindManager.o \
src/common/SoundSDL2.o \
src/common/StateManager.o \
src/common/ZipHandler.o
src/common/ZipHandler.o \
src/common/AudioQueue.o \
src/common/AudioSettings.o
MODULE_DIRS += \
src/common

View File

@ -513,7 +513,7 @@ void Debugger::nextFrame(int frames)
unlockSystem();
while(frames)
{
myOSystem.console().tia().update();
myOSystem.console().tia().update(myOSystem.console().emulationTiming().maxCyclesPerTimeslice());
--frames;
}
lockSystem();

View File

@ -168,6 +168,8 @@ void TiaOutputWidget::drawWidget(bool hilite)
uInt32 scanx, scany, scanoffset;
bool visible = instance().console().tia().electronBeamPos(scanx, scany);
scanoffset = width * scany + scanx;
uInt8* tiaOutputBuffer = instance().console().tia().outputBuffer();
TIASurface& tiaSurface(instance().frameBuffer().tiaSurface());
for(uInt32 y = 0, i = 0; y < height; ++y)
{
@ -175,7 +177,7 @@ void TiaOutputWidget::drawWidget(bool hilite)
for(uInt32 x = 0; x < width; ++x, ++i)
{
uInt8 shift = i >= scanoffset ? 1 : 0;
uInt32 pixel = instance().frameBuffer().tiaSurface().pixel(i, shift);
uInt32 pixel = tiaSurface.mapIndexedPixel(tiaOutputBuffer[i], shift);
*line_ptr++ = pixel;
*line_ptr++ = pixel;
}

View File

@ -55,6 +55,8 @@
#include "Version.hxx"
#include "TIAConstants.hxx"
#include "FrameLayout.hxx"
#include "AudioQueue.hxx"
#include "AudioSettings.hxx"
#include "frame-manager/FrameManager.hxx"
#include "frame-manager/FrameLayoutDetector.hxx"
#include "frame-manager/YStartDetector.hxx"
@ -75,17 +77,17 @@ namespace {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
const Properties& props)
const Properties& props, AudioSettings& audioSettings)
: myOSystem(osystem),
myEvent(osystem.eventHandler().event()),
myProperties(props),
myCart(std::move(cart)),
myDisplayFormat(""), // Unknown TV format @ start
myFramerate(0.0), // Unknown framerate @ start
myCurrentFormat(0), // Unknown format @ start,
myAutodetectedYstart(0),
myUserPaletteDefined(false),
myConsoleTiming(ConsoleTiming::ntsc)
myConsoleTiming(ConsoleTiming::ntsc),
myAudioSettings(audioSettings)
{
// Load user-defined palette for this ROM
loadUserPalette();
@ -93,7 +95,7 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
// Create subsystems for the console
my6502 = make_unique<M6502>(myOSystem.settings());
myRiot = make_unique<M6532>(*this, myOSystem.settings());
myTIA = make_unique<TIA>(*this, myOSystem.sound(), myOSystem.settings());
myTIA = make_unique<TIA>(*this, myOSystem.settings());
myFrameManager = make_unique<FrameManager>();
mySwitches = make_unique<Switches>(myEvent, myProperties, myOSystem.settings());
@ -146,7 +148,6 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
// Note that this can be overridden if a format is forced
// For example, if a PAL ROM is forced to be NTSC, it will use NTSC-like
// properties (60Hz, 262 scanlines, etc), but likely result in flicker
// The TIA will self-adjust the framerate if necessary
setTIAProperties();
if(myDisplayFormat == "NTSC")
{
@ -205,6 +206,10 @@ Console::~Console()
// Some smart controllers need to be informed that the console is going away
myLeftControl->close();
myRightControl->close();
// Close audio to prevent invalid access to myConsoleTiming from the audio
// callback
myOSystem.sound().close();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -381,6 +386,7 @@ void Console::setFormat(int format)
setTIAProperties();
myTIA->frameReset();
initializeVideo(); // takes care of refreshing the screen
initializeAudio(); // ensure that audio synthesis is set up to match emulation speed
}
myOSystem.frameBuffer().showMessage(message);
@ -569,37 +575,29 @@ FBInitStatus Console::initializeVideo(bool full)
}
setPalette(myOSystem.settings().getString("palette"));
// Set the correct framerate based on the format of the ROM
// This can be overridden by changing the framerate in the
// VideoDialog box or on the commandline, but it can't be saved
// (ie, framerate is now determined based on number of scanlines).
int framerate = myOSystem.settings().getInt("framerate");
if(framerate > 0) myFramerate = float(framerate);
myOSystem.setFramerate(myFramerate);
// Make sure auto-frame calculation is only enabled when necessary
myTIA->enableAutoFrame(framerate <= 0);
return fbstatus;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::initializeAudio()
{
// Initialize the sound interface.
// The # of channels can be overridden in the AudioDialog box or on
// the commandline, but it can't be saved.
int framerate = myOSystem.settings().getInt("framerate");
if(framerate > 0) myFramerate = float(framerate);
const string& sound = myProperties.get(Cartridge_Sound);
myOSystem.sound().close();
myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1);
myOSystem.sound().setFrameRate(myFramerate);
myOSystem.sound().open();
// Make sure auto-frame calculation is only enabled when necessary
myTIA->enableAutoFrame(framerate <= 0);
myEmulationTiming
.updatePlaybackRate(myOSystem.sound().getSampleRate())
.updatePlaybackPeriod(myOSystem.sound().getFragmentSize())
.updateAudioQueueExtraFragments(myAudioSettings.bufferSize())
.updateAudioQueueHeadroom(myAudioSettings.headroom())
.updateSpeedFactor(myOSystem.settings().getFloat("speed"));
(cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush();
(cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush();
(cout << "prebuffer fragment count: " << myEmulationTiming.prebufferFragmentCount() << std::endl).flush();
createAudioQueue();
myTIA->setAudioQueue(myAudioQueue);
myOSystem.sound().open(myAudioQueue, &myEmulationTiming);
}
/* Original frying research and code by Fred Quimby.
@ -729,16 +727,11 @@ void Console::setTIAProperties()
myDisplayFormat == "SECAM60")
{
// Assume we've got ~262 scanlines (NTSC-like format)
myFramerate = 60.0;
myConsoleInfo.InitialFrameRate = "60";
myTIA->setLayout(FrameLayout::ntsc);
}
else
{
// Assume we've got ~312 scanlines (PAL-like format)
myFramerate = 50.0;
myConsoleInfo.InitialFrameRate = "50";
// PAL ROMs normally need at least 250 lines
if (height != 0) height = std::max(height, 250u);
@ -747,6 +740,18 @@ void Console::setTIAProperties()
myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart);
myTIA->setHeight(height);
myEmulationTiming.updateFrameLayout(myTIA->frameLayout());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::createAudioQueue()
{
myAudioQueue = make_shared<AudioQueue>(
myEmulationTiming.audioFragmentSize(),
myEmulationTiming.audioQueueCapacity(),
myProperties.get(Cartridge_Sound) == "STEREO"
);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -967,11 +972,9 @@ void Console::generateColorLossPalette()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::setFramerate(float framerate)
float Console::getFramerate() const
{
myFramerate = framerate;
myOSystem.setFramerate(framerate);
myOSystem.sound().setFrameRate(framerate);
return myTIA->frameBufferFrameRate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1103,7 +1106,7 @@ uInt32 Console::ourPALPalette[256] = {
0x001759, 0, 0x00247c, 0, 0x1d469e, 0, 0x3f68c0, 0, // 288 b
0x618ae2, 0, 0x83acff, 0, 0xa5ceff, 0, 0xc7f0ff, 0,
0x12006d, 0, 0x34038f, 0, 0x5625b1, 0, 0x7847d3, 0, // 2a0 c
0x9a69f5, 0, 0xb48cff, 0, 0xc9adff, 0, 0xe1d1ff, 0, // was ..0xbc8bff, 0xdeadff, 0xffcfff,
0x9a69f5, 0, 0xb48cff, 0, 0xc9adff, 0, 0xe1d1ff, 0, // was ..0xbc8bff, 0xdeadff, 0xffcfff,
0x000070, 0, 0x161292, 0, 0x3834b4, 0, 0x5a56d6, 0, // 2b8 d
0x7c78f8, 0, 0x9e9aff, 0, 0xc0bcff, 0, 0xe2deff, 0,
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2d0 e

View File

@ -27,6 +27,8 @@ class M6532;
class Cartridge;
class CompuMate;
class Debugger;
class AudioQueue;
class AudioSettings;
#include "bspf.hxx"
#include "Control.hxx"
@ -36,6 +38,7 @@ class Debugger;
#include "Serializable.hxx"
#include "EventHandlerConstants.hxx"
#include "NTSCFilter.hxx"
#include "EmulationTiming.hxx"
#include "frame-manager/AbstractFrameManager.hxx"
/**
@ -49,7 +52,6 @@ struct ConsoleInfo
string Control0;
string Control1;
string DisplayFormat;
string InitialFrameRate;
};
/**
@ -79,7 +81,7 @@ class Console : public Serializable
@param props The properties for the cartridge
*/
Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
const Properties& props);
const Properties& props, AudioSettings& audioSettings);
/**
Destructor
@ -190,6 +192,11 @@ class Console : public Serializable
*/
void stateChanged(EventHandlerState state);
/**
Retrieve emulation timing provider.
*/
EmulationTiming& emulationTiming() { return myEmulationTiming; }
public:
/**
Toggle between NTSC/PAL/SECAM (and variants) display format.
@ -270,16 +277,9 @@ class Console : public Serializable
void changeHeight(int direction);
/**
Sets the framerate of the console, which in turn communicates
this to all applicable subsystems.
Returns the current framerate.
*/
void setFramerate(float framerate);
/**
Returns the framerate based on a number of factors
(whether 'framerate' is set, what display format is in use, etc)
*/
float getFramerate() const { return myFramerate; }
float getFramerate() const;
/**
Toggles the TIA bit specified in the method name.
@ -330,6 +330,11 @@ class Console : public Serializable
*/
void setTIAProperties();
/**
Create the audio queue
*/
void createAudioQueue();
/**
Adds the left and right controllers to the console.
*/
@ -389,6 +394,9 @@ class Console : public Serializable
// The frame manager instance that is used during emulation.
unique_ptr<AbstractFrameManager> myFrameManager;
// The audio fragment queue that connects TIA and audio driver
shared_ptr<AudioQueue> myAudioQueue;
// Pointer to the Cartridge (the debugger needs it)
unique_ptr<Cartridge> myCart;
@ -404,9 +412,6 @@ class Console : public Serializable
// The currently defined display format (NTSC/PAL/SECAM)
string myDisplayFormat;
// The currently defined display framerate
float myFramerate;
// Display format currently in use
uInt32 myCurrentFormat;
@ -423,6 +428,13 @@ class Console : public Serializable
// Contains timing information for this console
ConsoleTiming myConsoleTiming;
// Emulation timing provider. This ties together the timing of the core emulation loop
// and the parameters that govern audio synthesis
EmulationTiming myEmulationTiming;
// The audio settings
AudioSettings& myAudioSettings;
// Table of RGB values for NTSC, PAL and SECAM
static uInt32 ourNTSCPalette[256];
static uInt32 ourPALPalette[256];

View File

@ -0,0 +1,55 @@
//============================================================================
//
// MM MM 6666 555555 0000 2222
// MMMM MMMM 66 66 55 00 00 22 22
// MM MMM MM 66 55 00 00 22
// MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator"
// MM MM 66 66 55 00 00 22
// MM MM 66 66 55 55 00 00 22
// MM MM 6666 5555 0000 222222
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "DispatchResult.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DispatchResult::assertStatus(Status status) const
{
if (myStatus != status) throw runtime_error("invalid status for operation");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool DispatchResult::isSuccess() const
{
return myStatus == Status::debugger || myStatus == Status::ok;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DispatchResult::setOk(uInt32 cycles)
{
myStatus = Status::ok;
myCycles = cycles;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DispatchResult::setDebugger(uInt32 cycles, const string& message, int address, bool wasReadTrap)
{
myStatus = Status::debugger;
myCycles = cycles;
myMessage = message;
myAddress = address;
myWasReadTrap = wasReadTrap;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DispatchResult::setFatal(uInt32 cycles)
{
myCycles = cycles;
myStatus = Status::fatal;
}

View File

@ -0,0 +1,67 @@
//============================================================================
//
// MM MM 6666 555555 0000 2222
// MMMM MMMM 66 66 55 00 00 22 22
// MM MMM MM 66 55 00 00 22
// MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator"
// MM MM 66 66 55 00 00 22
// MM MM 66 66 55 55 00 00 22
// MM MM 6666 5555 0000 222222
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef DISPATCH_RESULT_HXX
#define DISPATCH_RESULT_HXX
#include "bspf.hxx"
class DispatchResult
{
public:
enum class Status { invalid, ok, debugger, fatal };
public:
DispatchResult() : myStatus(Status::invalid) {}
Status getStatus() const { return myStatus; }
uInt32 getCycles() const { return myCycles; }
const string& getMessage() const { assertStatus(Status::debugger); return myMessage; }
uInt16 getAddress() const { assertStatus(Status::debugger); return myAddress; }
bool wasReadTrap() const { assertStatus(Status::debugger); return myWasReadTrap; }
bool isSuccess() const;
void setOk(uInt32 cycles);
void setDebugger(uInt32 cycles, const string& message = "", int address = -1, bool wasReadTrap = false);
void setFatal(uInt32 cycles);
private:
void assertStatus(Status status) const;
private:
Status myStatus;
uInt32 myCycles;
string myMessage;
int myAddress;
bool myWasReadTrap;
};
#endif // DISPATCH_RESULT_HXX

View File

@ -0,0 +1,202 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#include "EmulationTiming.hxx"
namespace {
constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1;
uInt32 discreteDivCeil(uInt32 n, uInt32 d)
{
return n / d + ((n % d == 0) ? 0 : 1);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming::EmulationTiming(FrameLayout frameLayout) :
myFrameLayout(frameLayout),
myPlaybackRate(44100),
myPlaybackPeriod(512),
myAudioQueueExtraFragments(1),
myAudioQueueHeadroom(2),
mySpeedFactor(1)
{
recalculate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout)
{
myFrameLayout = frameLayout;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate)
{
myPlaybackRate = playbackRate;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod)
{
myPlaybackPeriod = playbackPeriod;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments)
{
myAudioQueueExtraFragments = audioQueueExtraFragments;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateAudioQueueHeadroom(uInt32 audioQueueHeadroom)
{
myAudioQueueHeadroom = audioQueueHeadroom;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateSpeedFactor(float speedFactor)
{
mySpeedFactor = speedFactor;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::maxCyclesPerTimeslice() const
{
return myMaxCyclesPerTimeslice;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::minCyclesPerTimeslice() const
{
return myMinCyclesPerTimeslice;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::linesPerFrame() const
{
return myLinesPerFrame;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerFrame() const
{
return myCyclesPerFrame;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::framesPerSecond() const
{
return myFramesPerSecond;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerSecond() const
{
return myCyclesPerSecond;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioFragmentSize() const
{
return myAudioFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioSampleRate() const
{
return myAudioSampleRate;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioQueueCapacity() const
{
return myAudioQueueCapacity;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::prebufferFragmentCount() const
{
return myPrebufferFragmentCount;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationTiming::recalculate()
{
switch (myFrameLayout) {
case FrameLayout::ntsc:
myLinesPerFrame = 262;
break;
case FrameLayout::pal:
myLinesPerFrame = 312;
break;
default:
throw runtime_error("invalid frame layout");
}
switch (myFrameLayout) {
case FrameLayout::ntsc:
myFramesPerSecond = round(mySpeedFactor * 60);
break;
case FrameLayout::pal:
myFramesPerSecond = round(mySpeedFactor * 50);
break;
default:
throw runtime_error("invalid frame layout");
}
myCyclesPerFrame = 76 * myLinesPerFrame;
myMaxCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame * 2);
myMinCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame / 2);
myCyclesPerSecond = myCyclesPerFrame * myFramesPerSecond;
myAudioFragmentSize = round(mySpeedFactor * AUDIO_HALF_FRAMES_PER_FRAGMENT * myLinesPerFrame);
myAudioSampleRate = 2 * myLinesPerFrame * myFramesPerSecond;
myPrebufferFragmentCount = discreteDivCeil(
myPlaybackPeriod * myAudioSampleRate,
myAudioFragmentSize * myPlaybackRate
) + myAudioQueueHeadroom;
myAudioQueueCapacity = std::max(
myPrebufferFragmentCount,
discreteDivCeil(myMaxCyclesPerTimeslice * myAudioSampleRate, myAudioFragmentSize * myCyclesPerSecond)
) + myAudioQueueExtraFragments;
}

View File

@ -0,0 +1,96 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef EMULATION_TIMING_HXX
#define EMULATION_TIMING_HXX
#include "bspf.hxx"
#include "FrameLayout.hxx"
class EmulationTiming {
public:
EmulationTiming(FrameLayout frameLayout = FrameLayout::ntsc);
EmulationTiming& updateFrameLayout(FrameLayout frameLayout);
EmulationTiming& updatePlaybackRate(uInt32 playbackRate);
EmulationTiming& updatePlaybackPeriod(uInt32 period);
EmulationTiming& updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments);
EmulationTiming& updateAudioQueueHeadroom(uInt32 audioQueueHeadroom);
EmulationTiming& updateSpeedFactor(float speedFactor);
uInt32 maxCyclesPerTimeslice() const;
uInt32 minCyclesPerTimeslice() const;
uInt32 linesPerFrame() const;
uInt32 cyclesPerFrame() const;
uInt32 framesPerSecond() const;
uInt32 cyclesPerSecond() const;
uInt32 audioFragmentSize() const;
uInt32 audioSampleRate() const;
uInt32 audioQueueCapacity() const;
uInt32 prebufferFragmentCount() const;
private:
void recalculate();
private:
FrameLayout myFrameLayout;
uInt32 myPlaybackRate;
uInt32 myPlaybackPeriod;
uInt32 myAudioQueueExtraFragments;
uInt32 myAudioQueueHeadroom;
uInt32 myMaxCyclesPerTimeslice;
uInt32 myMinCyclesPerTimeslice;
uInt32 myLinesPerFrame;
uInt32 myCyclesPerFrame;
uInt32 myFramesPerSecond;
uInt32 myCyclesPerSecond;
uInt32 myAudioFragmentSize;
uInt32 myAudioSampleRate;
uInt32 myAudioQueueCapacity;
uInt32 myPrebufferFragmentCount;
float mySpeedFactor;
private:
EmulationTiming(const EmulationTiming&) = delete;
EmulationTiming(EmulationTiming&&) = delete;
EmulationTiming& operator=(const EmulationTiming&) = delete;
EmulationTiming& operator=(EmulationTiming&&) = delete;
};
#endif // EMULATION_TIMING_HXX

View File

@ -0,0 +1,335 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <exception>
#include "EmulationWorker.hxx"
#include "DispatchResult.hxx"
#include "TIA.hxx"
using namespace std::chrono;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationWorker::EmulationWorker() : myPendingSignal(Signal::none), myState(State::initializing)
{
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
std::condition_variable threadInitialized;
myThread = std::thread(
&EmulationWorker::threadMain, this, &threadInitialized, &mutex
);
// Wait until the thread has acquired myThreadIsRunningMutex and moved on
while (myState == State::initializing) threadInitialized.wait(lock);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationWorker::~EmulationWorker()
{
// This has to run in a block in order to release the mutex before joining
{
std::unique_lock<std::mutex> lock(myThreadIsRunningMutex);
if (myState != State::exception) {
signalQuit();
myWakeupCondition.notify_one();
}
}
myThread.join();
handlePossibleException();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handlePossibleException()
{
if (myState == State::exception && myPendingException) {
std::exception_ptr ex = myPendingException;
// Make sure that the exception is not thrown a second time (destructor!!!)
myPendingException = nullptr;
std::rethrow_exception(ex);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia)
{
// Wait until any pending signal has been processed
waitUntilPendingSignalHasProcessed();
// Run in a block to release the mutex before notifying; this avoids an unecessary
// block that will waste a timeslice
{
// Aquire the mutex -> wait until the thread is suspended
std::unique_lock<std::mutex> lock(myThreadIsRunningMutex);
// Pass on possible exceptions
handlePossibleException();
// Make sure that we don't overwrite the exit condition.
// This case is hypothetical and cannot happen, but handling it does not hurt, either
if (myPendingSignal == Signal::quit) return;
// NB: The thread does not suspend execution in State::initialized
if (myState != State::waitingForResume)
fatal("start called on running or dead worker");
// Store the parameters for emulation
myTia = tia;
myCyclesPerSecond = cyclesPerSecond;
myMaxCycles = maxCycles;
myMinCycles = minCycles;
myDispatchResult = dispatchResult;
// Raise the signal...
myPendingSignal = Signal::resume;
}
// ... and wakeup the thread
myWakeupCondition.notify_one();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt64 EmulationWorker::stop()
{
// See EmulationWorker::start above for the gory details
waitUntilPendingSignalHasProcessed();
uInt64 totalCycles;
{
std::unique_lock<std::mutex> lock(myThreadIsRunningMutex);
// Paranoia: make sure that we don't doublecount an emulation timeslice
totalCycles = myTotalCycles;
myTotalCycles = 0;
handlePossibleException();
if (myPendingSignal == Signal::quit) return totalCycles;
// If the worker has stopped on its own, we return
if (myState == State::waitingForResume) return totalCycles;
// NB: The thread does not suspend execution in State::initialized or State::running
if (myState != State::waitingForStop)
fatal("stop called on a dead worker");
myPendingSignal = Signal::stop;
}
myWakeupCondition.notify_one();
return totalCycles;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex)
{
std::unique_lock<std::mutex> lock(myThreadIsRunningMutex);
try {
{
// Wait until our parent releases the lock and sleeps
std::lock_guard<std::mutex> guard(*initializationMutex);
// Update the state...
myState = State::initialized;
// ... and wake up our parent to notifiy that we have initialized. From this point, the
// parent can safely assume that we are running while the mutex is locked.
initializedCondition->notify_one();
}
// Loop until we have an exit condition
while (myPendingSignal != Signal::quit) handleWakeup(lock);
}
catch (...) {
// Store away the exception and the state accordingly
myPendingException = std::current_exception();
myState = State::exception;
// Raising the exit condition is consistent and makes shure that the main thread
// will not deadlock if an exception is raised while it is waiting for a signal
// to be processed.
signalQuit();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeup(std::unique_lock<std::mutex>& lock)
{
switch (myState) {
case State::initialized:
// Enter waitingForResume and sleep after initialization
myState = State::waitingForResume;
myWakeupCondition.wait(lock);
break;
case State::waitingForResume:
handleWakeupFromWaitingForResume(lock);
break;
case State::waitingForStop:
handleWakeupFromWaitingForStop(lock);
break;
default:
fatal("wakeup in invalid worker state");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock)
{
switch (myPendingSignal) {
case Signal::resume:
// Clear the pending signal and notify the main thread
clearSignal();
// Reset virtual clock and cycle counter
myVirtualTime = high_resolution_clock::now();
myTotalCycles = 0;
// Enter emulation. This will emulate a timeslice and set the state upon completion.
dispatchEmulation(lock);
break;
case Signal::none:
// Reenter sleep on spurious wakeups
myWakeupCondition.wait(lock);
break;
case Signal::quit:
break;
default:
fatal("invalid signal while waiting for resume");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock)
{
switch (myPendingSignal) {
case Signal::stop:
// Clear the pending signal and notify the main thread
clearSignal();
// Enter waiting for resume and sleep
myState = State::waitingForResume;
myWakeupCondition.wait(lock);
break;
case Signal::none:
if (myVirtualTime <= high_resolution_clock::now())
// The time allotted to the emulation timeslice has passed and we haven't been stopped?
// -> go for another emulation timeslice
dispatchEmulation(lock);
else
// Wakeup was spurious, reenter sleep
myWakeupCondition.wait_until(lock, myVirtualTime);
break;
case Signal::quit:
break;
default:
fatal("invalid signal while waiting for stop");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::dispatchEmulation(std::unique_lock<std::mutex>& lock)
{
// Technically, we could do without State::running, but it is cleaner and might be useful in the future
myState = State::running;
uInt64 totalCycles = 0;
do {
myTia->update(*myDispatchResult, totalCycles > 0 ? myMinCycles - totalCycles : myMaxCycles);
totalCycles += myDispatchResult->getCycles();
} while (totalCycles < myMinCycles && myDispatchResult->getStatus() == DispatchResult::Status::ok);
myTotalCycles += totalCycles;
bool continueEmulating = false;
if (myDispatchResult->getStatus() == DispatchResult::Status::ok) {
// If emulation finished successfully, we are free to go for another round
duration<double> timesliceSeconds(static_cast<double>(totalCycles) / static_cast<double>(myCyclesPerSecond));
myVirtualTime += duration_cast<high_resolution_clock::duration>(timesliceSeconds);
// If we aren't fast enough to keep up with the emulation, we stop immediatelly to avoid
// starving the system for processing time --- emulation will stutter anyway.
continueEmulating = myVirtualTime > high_resolution_clock::now();
}
if (continueEmulating) {
// If we are free to continue emulating, we sleep until either the timeslice has passed or we
// have been signalled from the main thread
myState = State::waitingForStop;
myWakeupCondition.wait_until(lock, myVirtualTime);
} else {
// If can't continue, we just stop and wait to be signalled
myState = State::waitingForResume;
myWakeupCondition.wait(lock);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::clearSignal()
{
{
std::unique_lock<std::mutex> lock(mySignalChangeMutex);
myPendingSignal = Signal::none;
}
mySignalChangeCondition.notify_one();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::signalQuit()
{
{
std::unique_lock<std::mutex> lock(mySignalChangeMutex);
myPendingSignal = Signal::quit;
}
mySignalChangeCondition.notify_one();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::waitUntilPendingSignalHasProcessed()
{
std::unique_lock<std::mutex> lock(mySignalChangeMutex);
// White until there is no pending signal (or the exit condition has been raised)
while (myPendingSignal != Signal::none && myPendingSignal != Signal::quit)
mySignalChangeCondition.wait(lock);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationWorker::fatal(string message)
{
(cerr << "FATAL in emulation worker: " << message << std::endl).flush();
throw runtime_error(message);
}

View File

@ -0,0 +1,213 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
/*
* This class is the core of stella's scheduling. Scheduling is a two step process
* that is shared between the main loop in OSystem and this class.
*
* In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop
* instructs the emulation worker to start emulation on a separate thread and then proceeds
* to render the last frame produced by the TIA (if any). After the frame has been rendered,
* the worker is stopped, and the main thread sleeps until the time allotted to the emulation
* timeslice (as calculated from the 6507 cycles that have passed) has been reached. After
* that, it iterates.
*
* The emulation worker does its own microscheduling. After emulating a timeslice, it sleeps
* until either the allotted time is up or it has been signalled to stop. If the time is up
* without being signalled, the worker will emulate another timeslice, etc.
*
* In combination, the scheduling in the main loop and the microscheduling in the worker
* ensure that the emulation continues to run even if rendering blocks, ensuring the real
* time scheduling required for cycle exact audio to work.
*/
#ifndef EMULATION_WORKER_HXX
#define EMULATION_WORKER_HXX
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <exception>
#include <chrono>
#include "bspf.hxx"
class TIA;
class DispatchResult;
class EmulationWorker
{
public:
/**
The constructor starts the worker thread and waits until it has initialized.
*/
EmulationWorker();
/**
The destructor signals quit to the worker and joins.
*/
~EmulationWorker();
/**
Wake up the worker and start emulation with the specified parameters.
*/
void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia);
/**
Stop emulation and return the number of 6507 cycles emulated.
*/
uInt64 stop();
private:
/**
Check whether an exception occurred on the thread and rethrow if appicable.
*/
void handlePossibleException();
/**
The main thread entry point.
Passing references into a thread is awkward and requires std::ref -> use pointers here
*/
void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex);
/**
Handle thread wakeup after sleep depending on the thread state.
*/
void handleWakeup(std::unique_lock<std::mutex>& lock);
/**
Handle wakeup while sleeping and waiting to be resumed.
*/
void handleWakeupFromWaitingForResume(std::unique_lock<std::mutex>& lock);
/**
Handle wakeup while sleeping and waiting to be stopped (or for the timeslice
to expire).
*/
void handleWakeupFromWaitingForStop(std::unique_lock<std::mutex>& lock);
/**
Run the emulation, adjust the thread state according to the result and sleep.
*/
void dispatchEmulation(std::unique_lock<std::mutex>& lock);
/**
Clear any pending signal and wake up the main thread (if it is waiting for the signal
to be cleared).
*/
void clearSignal();
/**
* Signal quit and wake up the main thread if applicable.
*/
void signalQuit();
/**
Wait and sleep until a pending signal has been processed (or quit sigmalled).
This is called from the main thread.
*/
void waitUntilPendingSignalHasProcessed();
/**
Log a fatal error to cerr and throw a runtime exception.
*/
void fatal(string message);
private:
/**
Thread state.
*/
enum class State {
// Initial state
initializing,
// Thread has initialized. From the point, myThreadIsRunningMutex is locked if and only if
// the thread is running.
initialized,
// Sleeping and waiting for emulation to be resumed
waitingForResume,
// Running and emulating
running,
// Sleeping and waiting for emulation to be stopped
waitingForStop,
// An exception occurred and the thread has terminated (or is terminating)
exception
};
/**
Thread behavior is controlled by signals that are raised prior to waking up the thread.
*/
enum class Signal {
// Resume emulation
resume,
// Stop emulation
stop,
// Quit (either during destruction or after an exception)
quit,
// No pending signal
none
};
private:
// Worker thread
std::thread myThread;
// Condition variable for waking up the thread
std::condition_variable myWakeupCondition;
// The thread is running if and only if while this mutex is locked
std::mutex myThreadIsRunningMutex;
// Condition variable to signal changes to the pending signal
std::condition_variable mySignalChangeCondition;
// This mutex guards reading and writing the pending signal.
std::mutex mySignalChangeMutex;
// Any exception on the worker thread is saved here to be rethrown on the main thread.
std::exception_ptr myPendingException;
// Any pending signal (or Signal::none)
Signal myPendingSignal;
// The initial access to myState is not synchronized -> make this atomic
std::atomic<State> myState;
// Emulation parameters
TIA* myTia;
uInt32 myCyclesPerSecond;
uInt32 myMaxCycles;
uInt32 myMinCycles;
DispatchResult* myDispatchResult;
// Total number of cycles during this emulation run
uInt64 myTotalCycles;
// 6507 time
std::chrono::time_point<std::chrono::high_resolution_clock> myVirtualTime;
private:
EmulationWorker(const EmulationWorker&) = delete;
EmulationWorker(EmulationWorker&&) = delete;
EmulationWorker& operator=(const EmulationWorker&) = delete;
EmulationWorker& operator=(EmulationWorker&&) = delete;
};
#endif // EMULATION_WORKER_HXX

View File

@ -48,13 +48,9 @@ FrameBuffer::FrameBuffer(OSystem& osystem)
myPausedCount(0),
myStatsEnabled(false),
myLastScanlines(0),
myLastFrameRate(60),
myGrabMouse(false),
myCurrentModeList(nullptr),
myTotalTime(0),
myTotalFrames(0)
{
}
myCurrentModeList(nullptr)
{}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FrameBuffer::~FrameBuffer()
@ -272,29 +268,8 @@ void FrameBuffer::update()
switch(myOSystem.eventHandler().state())
{
case EventHandlerState::EMULATION:
{
// Run the console for one frame
// Note that the debugger can cause a breakpoint to occur, which changes
// the EventHandler state 'behind our back' - we need to check for that
myOSystem.console().tia().update();
#ifdef DEBUGGER_SUPPORT
if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break;
#endif
if(myOSystem.eventHandler().frying())
myOSystem.console().fry();
// And update the screen
myTIASurface->render();
// Show frame statistics
if(myStatsMsg.enabled)
drawFrameStats();
else
myLastFrameRate = myOSystem.console().getFramerate();
myLastScanlines = myOSystem.console().tia().scanlinesLastFrame();
myPausedCount = 0;
break; // EventHandlerState::EMULATION
}
// Do nothing; emulation mode is handled separately (see below)
break;
case EventHandlerState::PAUSE:
{
@ -356,6 +331,30 @@ void FrameBuffer::update()
postFrameUpdate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::updateInEmulationMode()
{
// Determine which mode we are in (from the EventHandler)
// Take care of S_EMULATE mode here, otherwise let the GUI
// figure out what to draw
myTIASurface->render();
// Show frame statistics
if(myStatsMsg.enabled)
drawFrameStats();
myLastScanlines = myOSystem.console().tia().frameBufferScanlinesLastFrame();
myPausedCount = 0;
// Draw any pending messages
if(myMsg.enabled)
drawMessage();
// Do any post-frame stuff
postFrameUpdate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::showMessage(const string& message, MessagePosition position,
bool force)
@ -367,6 +366,7 @@ void FrameBuffer::showMessage(const string& message, MessagePosition position,
// Precompute the message coordinates
myMsg.text = message;
myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds
if(myMsg.counter == 0) myMsg.counter = 60;
myMsg.color = kBtnTextColor;
myMsg.w = font().getStringWidth(myMsg.text) + 10;
@ -389,9 +389,9 @@ void FrameBuffer::drawFrameStats()
myStatsMsg.surface->invalidate();
// draw scanlines
color = myOSystem.console().tia().scanlinesLastFrame() != myLastScanlines ?
color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ?
uInt32(kDbgColorRed) : myStatsMsg.color;
std::snprintf(msg, 30, "%3u", myOSystem.console().tia().scanlinesLastFrame());
std::snprintf(msg, 30, "%3u", myOSystem.console().tia().frameBufferScanlinesLastFrame());
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
xPos += font().getStringWidth(msg);
@ -402,23 +402,10 @@ void FrameBuffer::drawFrameStats()
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
xPos += font().getStringWidth(msg);
// draw the effective framerate
float frameRate;
const TimingInfo& ti = myOSystem.timingInfo();
if(ti.totalFrames - myTotalFrames >= myLastFrameRate)
{
frameRate = 1000000.0 * (ti.totalFrames - myTotalFrames) / (ti.totalTime - myTotalTime);
if(frameRate > myOSystem.console().getFramerate() + 1)
frameRate = 1;
myTotalFrames = ti.totalFrames;
myTotalTime = ti.totalTime;
}
else
frameRate = myLastFrameRate;
std::snprintf(msg, 30, " @ %5.2ffps", frameRate);
std::snprintf(msg, 30, " @ %5.2ffps", myOSystem.console().getFramerate());
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
myLastFrameRate = frameRate;
// draw bankswitching type
string bsinfo = info.BankSwitch +
@ -751,8 +738,7 @@ void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight)
myDesktopSize.w, myDesktopSize.h);
// Aspect ratio
bool ntsc = myOSystem.console().about().InitialFrameRate == "60";
uInt32 aspect = myOSystem.settings().getInt(ntsc ?
uInt32 aspect = myOSystem.settings().getInt(myOSystem.console().timing() == ConsoleTiming::ntsc ?
"tia.aspectn" : "tia.aspectp");
// Figure our the smallest zoom level we can use

View File

@ -113,10 +113,16 @@ class FrameBuffer
/**
Updates the display, which depending on the current mode could mean
drawing the TIA, any pending menus, etc.
drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles
spent during emulation, or -1 if not applicable.
*/
void update();
/**
There is a dedicated update method for emulation mode.
*/
void updateInEmulationMode();
/**
Shows a message onscreen.
@ -522,7 +528,6 @@ class FrameBuffer
Message myStatsMsg;
bool myStatsEnabled;
uInt32 myLastScanlines;
float myLastFrameRate;
bool myGrabMouse;
@ -540,9 +545,6 @@ class FrameBuffer
// Holds UI palette data (standard and classic colours)
static uInt32 ourGUIColors[3][kNumColors-256];
uInt64 myTotalTime;
uInt64 myTotalFrames;
private:
// Following constructors and assignment operators not supported
FrameBuffer() = delete;

View File

@ -47,6 +47,7 @@
#include "System.hxx"
#include "M6502.hxx"
#include "DispatchResult.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
M6502::M6502(const Settings& settings)
@ -208,9 +209,9 @@ inline void M6502::handleHalt()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502::execute(uInt32 number)
void M6502::execute(uInt32 number, DispatchResult& result)
{
const bool status = _execute(number);
_execute(number, result);
#ifdef DEBUGGER_SUPPORT
// Debugger hack: this ensures that stepping a "STA WSYNC" will actually end at the
@ -218,18 +219,27 @@ bool M6502::execute(uInt32 number)
// the halt to take effect). This is safe because as we know that the next cycle will be a read
// cycle anyway.
handleHalt();
// Make sure that the hardware state matches the current system clock. This is necessary
// to maintain a consistent state for the debugger after stepping.
mySystem->tia().updateEmulation();
mySystem->m6532().updateEmulation();
#endif
return status;
// Make sure that the hardware state matches the current system clock. This is necessary
// to maintain a consistent state for the debugger after stepping and to make sure
// that audio samples are generated for the whole timeslice.
mySystem->tia().updateEmulation();
mySystem->m6532().updateEmulation();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline bool M6502::_execute(uInt32 number)
bool M6502::execute(uInt32 number)
{
DispatchResult result;
_execute(number, result);
return result.isSuccess();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline void M6502::_execute(uInt32 cycles, DispatchResult& result)
{
// Clear all of the execution status bits except for the fatal error bit
myExecutionStatus &= FatalErrorBit;
@ -239,32 +249,43 @@ inline bool M6502::_execute(uInt32 number)
M6532& riot = mySystem->m6532();
#endif
uInt64 previousCycles = mySystem->cycles();
uInt32 currentCycles = 0;
// Loop until execution is stopped or a fatal error occurs
for(;;)
{
for(; !myExecutionStatus && (number != 0); --number)
while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU)
{
#ifdef DEBUGGER_SUPPORT
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
{
bool read = myJustHitReadTrapFlag;
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
// Don't break if we haven't actually executed anything yet
if (currentCycles > 0) {
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
{
bool read = myJustHitReadTrapFlag;
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
if (startDebugger(myHitTrapInfo.message, myHitTrapInfo.address, read)) return true;
result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read);
return;
}
if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) {
result.setDebugger(currentCycles, "BP: ", PC);
return;
}
int cond = evalCondBreaks();
if(cond > -1)
{
stringstream msg;
msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond];
result.setDebugger(currentCycles, msg.str());
return;
}
}
if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC) && startDebugger("BP: ", PC))
return true;
int cond = evalCondBreaks();
if(cond > -1)
{
stringstream msg;
msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond];
if (startDebugger(msg.str())) return true;
}
cond = evalCondSaveStates();
int cond = evalCondSaveStates();
if(cond > -1)
{
stringstream msg;
@ -294,6 +315,8 @@ inline bool M6502::_execute(uInt32 number)
myExecutionStatus |= FatalErrorBit;
}
currentCycles = (mySystem->cycles() - previousCycles);
#ifdef DEBUGGER_SUPPORT
if(myStepStateByInstruction)
{
@ -318,21 +341,24 @@ inline bool M6502::_execute(uInt32 number)
if(myExecutionStatus & StopExecutionBit)
{
// Yes, so answer that everything finished fine
return true;
result.setOk(currentCycles);
return;
}
// See if a fatal error has occured
if(myExecutionStatus & FatalErrorBit)
{
// Yes, so answer that something when wrong
return false;
result.setFatal(currentCycles + icycles);
return;
}
// See if we've executed the specified number of instructions
if(number == 0)
if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU)
{
// Yes, so answer that everything finished fine
return true;
result.setOk(currentCycles);
return;
}
}
}
@ -606,15 +632,4 @@ void M6502::updateStepStateByInstruction()
myTrapConds.size();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502::startDebugger(const string& message, int address, bool read)
{
handleHalt();
mySystem->tia().updateEmulation();
mySystem->m6532().updateEmulation();
return myDebugger->start(message, address, read);
}
#endif // DEBUGGER_SUPPORT

View File

@ -22,6 +22,7 @@
class Settings;
class System;
class DispatchResult;
#ifdef DEBUGGER_SUPPORT
class Debugger;
@ -110,10 +111,14 @@ class M6502 : public Serializable
is executed, someone stops execution, or an error occurs. Answers
true iff execution stops normally.
@param number Indicates the number of instructions to execute
@return true iff execution stops normally
@param cycles Indicates the number of cycles to execute. Not that the actual
granularity of the CPU is instructions, so this is only accurate up to
a couple of cycles
@param result A DispatchResult object that will transport the result
*/
bool execute(uInt32 number);
void execute(uInt32 cycles, DispatchResult& result);
bool execute(uInt32 cycles);
/**
Tell the processor to stop executing instructions. Invoking this
@ -318,7 +323,7 @@ class M6502 : public Serializable
This is the actual dispatch function that does the grunt work. M6502::execute
wraps it and makes sure that any pending halt is processed before returning.
*/
bool _execute(uInt32 number);
void _execute(uInt32 cycles, DispatchResult& result);
#ifdef DEBUGGER_SUPPORT
/**
@ -326,12 +331,6 @@ class M6502 : public Serializable
with the CPU and update the flag accordingly.
*/
void updateStepStateByInstruction();
/**
Make sure that the current hardware state is up to date (TIA & RIOT) and dispatch
debugger.
*/
bool startDebugger(const string& message = "", int address = -1, bool read = true);
#endif // DEBUGGER_SUPPORT
private:

View File

@ -17,11 +17,6 @@
#include <cassert>
#include <ctime>
#ifdef HAVE_GETTIMEOFDAY
#include <sys/time.h>
#endif
#include "bspf.hxx"
#include "MediaFactory.hxx"
@ -35,6 +30,8 @@
#include "CheatManager.hxx"
#endif
#include <chrono>
#include "FSNode.hxx"
#include "MD5.hxx"
#include "Cart.hxx"
@ -55,17 +52,19 @@
#include "SerialPort.hxx"
#include "StateManager.hxx"
#include "Version.hxx"
#include "TIA.hxx"
#include "DispatchResult.hxx"
#include "EmulationWorker.hxx"
#include "OSystem.hxx"
using namespace std::chrono;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OSystem::OSystem()
: myLauncherUsed(false),
myQuitLoop(false)
{
// Calculate startup time
myMillisAtStart = uInt32(time(nullptr) * 1000);
// Get built-in features
#ifdef SOUND_SUPPORT
myFeatures += "Sound ";
@ -91,6 +90,7 @@ OSystem::OSystem()
myBuildInfo = info.str();
mySettings = MediaFactory::createSettings(*this);
myAudioSettings = AudioSettings(mySettings.get());
myRandom = make_unique<Random>(*this);
}
@ -283,16 +283,6 @@ void OSystem::setConfigFile(const string& file)
myConfigFile = FilesystemNode(file).getPath();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::setFramerate(float framerate)
{
if(framerate > 0.0)
{
myDisplayFrameRate = framerate;
myTimePerFrame = uInt32(1000000.0 / myDisplayFrameRate);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FBInitStatus OSystem::createFrameBuffer()
{
@ -332,9 +322,9 @@ FBInitStatus OSystem::createFrameBuffer()
void OSystem::createSound()
{
if(!mySound)
mySound = MediaFactory::createAudio(*this);
mySound = MediaFactory::createAudio(*this, myAudioSettings);
#ifndef SOUND_SUPPORT
mySettings->setValue("sound", false);
myAudioSettings.setEnabled(false);
#endif
}
@ -408,9 +398,6 @@ string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
<< getROMInfo(*myConsole) << endl;
logMessage(buf.str(), 1);
// Update the timing info for a new console run
resetLoopTiming();
myFrameBuffer->setCursorState();
// Also check if certain virtual buttons should be held down
@ -450,8 +437,6 @@ bool OSystem::createLauncher(const string& startdir)
myLauncher->reStack();
myFrameBuffer->setCursorState();
setFramerate(30);
resetLoopTiming();
status = true;
}
else
@ -565,7 +550,7 @@ unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string&
// Finally, create the cart with the correct properties
if(cart)
console = make_unique<Console>(*this, cart, props);
console = make_unique<Console>(*this, cart, props, myAudioSettings);
}
return console;
@ -627,15 +612,6 @@ string OSystem::getROMInfo(const Console& console)
return buf.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::resetLoopTiming()
{
myTimingInfo.start = myTimingInfo.virt = getTicks();
myTimingInfo.current = 0;
myTimingInfo.totalTime = 0;
myTimingInfo.totalFrames = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::validatePath(string& path, const string& setting,
const string& defaultpath)
@ -653,68 +629,105 @@ void OSystem::validatePath(string& path, const string& setting,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt64 OSystem::getTicks() const
{
#ifdef HAVE_GETTIMEOFDAY
// Gettimeofday natively refers to the UNIX epoch (a set time in the past)
timeval now;
gettimeofday(&now, nullptr);
return duration_cast<duration<uInt64, std::ratio<1, 1000000> > >(system_clock::now().time_since_epoch()).count();
}
return uInt64(now.tv_sec) * 1000000 + now.tv_usec;
#else
// We use SDL_GetTicks, but add in the time when the application was
// initialized. This is necessary, since SDL_GetTicks only measures how
// long SDL has been running, which can be the same between multiple runs
// of the application.
return uInt64(SDL_GetTicks() + myMillisAtStart) * 1000;
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float OSystem::frameRate() const
{
return myConsole ? myConsole->getFramerate() : 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
{
if (!myConsole) return 0.;
TIA& tia(myConsole->tia());
EmulationTiming& timing(myConsole->emulationTiming());
DispatchResult dispatchResult;
// Check whether we have a frame pending for rendering...
bool framePending = tia.newFramePending();
// ... and copy it to the frame buffer. It is important to do this before
// the worker is started to avoid racing.
if (framePending) tia.renderToFrameBuffer();
// Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time
// and will run until we stop the worker.
emulationWorker.start(
timing.cyclesPerSecond(),
timing.maxCyclesPerTimeslice(),
timing.minCyclesPerTimeslice(),
&dispatchResult,
&tia
);
// Render the frame. This may block, but emulation will continue to run on the worker, so the
// audio pipeline is kept fed :)
if (framePending) myFrameBuffer->updateInEmulationMode();
// Stop the worker and wait until it has finished
uInt64 totalCycles = emulationWorker.stop();
// Break or trap? -> start debugger
if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start();
// Handle frying
if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
myConsole->fry();
// Return the 6507 time used in seconds
return static_cast<double>(totalCycles) / static_cast<double>(timing.cyclesPerSecond());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::mainLoop()
{
if(mySettings->getString("timing") == "sleep")
// Sleep-based wait: good for CPU, bad for graphical sync
bool busyWait = mySettings->getString("timing") != "sleep";
// 6507 time
time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
// The emulation worker
EmulationWorker emulationWorker;
for(;;)
{
// Sleep-based wait: good for CPU, bad for graphical sync
for(;;)
{
myTimingInfo.start = getTicks();
myEventHandler->poll(myTimingInfo.start);
if(myQuitLoop) break; // Exit if the user wants to quit
myEventHandler->poll(getTicks());
if(myQuitLoop) break; // Exit if the user wants to quit
double timesliceSeconds;
if (myEventHandler->state() == EventHandlerState::EMULATION)
// Dispatch emulation and render frame (if applicable)
timesliceSeconds = dispatchEmulation(emulationWorker);
else {
// Render the GUI with 30 Hz in all other modes
timesliceSeconds = 1. / 30.;
myFrameBuffer->update();
myTimingInfo.current = getTicks();
myTimingInfo.virt += myTimePerFrame;
// Timestamps may periodically go out of sync, particularly on systems
// that can have 'negative time' (ie, when the time seems to go backwards)
// This normally results in having a very large delay time, so we check
// for that and reset the timers when appropriate
if((myTimingInfo.virt - myTimingInfo.current) > (myTimePerFrame << 1))
{
myTimingInfo.current = myTimingInfo.virt = getTicks();
}
if(myTimingInfo.current < myTimingInfo.virt)
SDL_Delay(uInt32(myTimingInfo.virt - myTimingInfo.current) / 1000);
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
myTimingInfo.totalFrames++;
}
}
else
{
// Busy-wait: bad for CPU, good for graphical sync
for(;;)
{
myTimingInfo.start = getTicks();
myEventHandler->poll(myTimingInfo.start);
if(myQuitLoop) break; // Exit if the user wants to quit
myFrameBuffer->update();
myTimingInfo.virt += myTimePerFrame;
while(getTicks() < myTimingInfo.virt)
; // busy-wait
duration<double> timeslice(timesliceSeconds);
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
time_point<high_resolution_clock> now = high_resolution_clock::now();
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
myTimingInfo.totalFrames++;
// We allow 6507 time to lag behind by one frame max
double maxLag = myConsole
? (
static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
static_cast<double>(myConsole->emulationTiming().cyclesPerSecond())
)
: 0;
if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
// If 6507 time is lagging behind more than one frame we reset it to real time
virtualTime = now;
else if (virtualTime > now) {
// Wait until we have caught up with 6507 time
if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) {
while (high_resolution_clock::now() < virtualTime);
}
else std::this_thread::sleep_until(virtualTime);
}
}

View File

@ -38,19 +38,13 @@ class Settings;
class Sound;
class StateManager;
class VideoDialog;
class EmulationWorker;
#include "FSNode.hxx"
#include "FrameBufferConstants.hxx"
#include "EventHandlerConstants.hxx"
#include "bspf.hxx"
struct TimingInfo {
uInt64 start;
uInt64 current;
uInt64 virt;
uInt64 totalTime;
uInt64 totalFrames;
};
#include "AudioSettings.hxx"
/**
This class provides an interface for accessing operating system specific
@ -132,6 +126,11 @@ class OSystem
Console& console() const { return *myConsole; }
bool hasConsole() const;
/**
Get the audio settings ovject.
*/
AudioSettings& audioSettings() { return myAudioSettings; }
/**
Get the serial port of the system.
@ -213,26 +212,11 @@ class OSystem
CheatManager& cheat() const { return *myCheatManager; }
#endif
/**
Set the framerate for the video system. It's placed in this class since
the mainLoop() method is defined here.
@param framerate The video framerate to use
*/
virtual void setFramerate(float framerate);
/**
Set all config file paths for the OSystem.
*/
void setConfigPaths();
/**
Get the current framerate for the video system.
@return The video framerate currently in use
*/
float frameRate() const { return myDisplayFrameRate; }
/**
Return the default full/complete directory name for storing data.
*/
@ -383,12 +367,6 @@ class OSystem
*/
const string& logMessages() const { return myLogMessages; }
/**
Return timing information (start time of console, current
number of frames rendered, etc.
*/
const TimingInfo& timingInfo() const { return myTimingInfo; }
public:
//////////////////////////////////////////////////////////////////////
// The following methods are system-specific and can be overrided in
@ -406,6 +384,8 @@ class OSystem
*/
virtual uInt64 getTicks() const;
float frameRate() const;
/**
This method runs the main loop. Since different platforms
may use different timing methods and/or algorithms, this method can
@ -504,18 +484,12 @@ class OSystem
// The list of log messages
string myLogMessages;
// Number of times per second to iterate through the main loop
float myDisplayFrameRate;
// Time per frame for a video update, based on the current framerate
uInt32 myTimePerFrame;
// The time (in milliseconds) from the UNIX epoch when the application starts
uInt32 myMillisAtStart;
// Indicates whether to stop the main loop
bool myQuitLoop;
// Audio settings
AudioSettings myAudioSettings;
private:
string myBaseDir;
string myStateDir;
@ -536,9 +510,6 @@ class OSystem
string myFeatures;
string myBuildInfo;
// Indicates whether the main processing loop should proceed
TimingInfo myTimingInfo;
private:
/**
Creates the various framebuffers/renderers available in this system.
@ -590,12 +561,6 @@ class OSystem
*/
string getROMInfo(const Console& console);
/**
Initializes the timing so that the mainloop is reset to its
initial values.
*/
void resetLoopTiming();
/**
Validate the directory name, and create it if necessary.
Also, update the settings with the new name. For now, validation
@ -608,6 +573,8 @@ class OSystem
void validatePath(string& path, const string& setting,
const string& defaultpath);
double dispatchEmulation(EmulationWorker& emulationWorker);
// Following constructors and assignment operators not supported
OSystem(const OSystem&) = delete;
OSystem(OSystem&&) = delete;

View File

@ -115,7 +115,7 @@ class PropertiesSet
/**
Return the size of the myExternalProps list
*/
uInt32 size() { return myExternalProps.size(); }
uInt64 size() const { return myExternalProps.size(); }
private:
using PropsList = std::map<string, Properties>;

View File

@ -21,6 +21,7 @@
#include "OSystem.hxx"
#include "Version.hxx"
#include "AudioSettings.hxx"
#ifdef DEBUGGER_SUPPORT
#include "DebuggerDialog.hxx"
@ -34,7 +35,7 @@ Settings::Settings(OSystem& osystem)
{
// Video-related options
setInternal("video", "");
setInternal("framerate", "0");
setInternal("speed", "1.0");
setInternal("vsync", "true");
setInternal("fullscreen", "false");
setInternal("center", "false");
@ -69,10 +70,14 @@ Settings::Settings(OSystem& osystem)
setInternal("tv.bleed", "0.0");
// Sound options
setInternal("sound", "true");
setInternal("fragsize", "512");
setInternal("freq", "31400");
setInternal("volume", "100");
setInternal(AudioSettings::SETTING_ENABLED, AudioSettings::DEFAULT_ENABLED);
setInternal(AudioSettings::SETTING_PRESET, static_cast<int>(AudioSettings::DEFAULT_PRESET));
setInternal(AudioSettings::SETTING_SAMPLE_RATE, AudioSettings::DEFAULT_SAMPLE_RATE);
setInternal(AudioSettings::SETTING_FRAGMENT_SIZE, AudioSettings::DEFAULT_FRAGMENT_SIZE);
setInternal(AudioSettings::SETTING_BUFFER_SIZE, AudioSettings::DEFAULT_BUFFER_SIZE);
setInternal(AudioSettings::SETTING_HEADROOM, AudioSettings::DEFAULT_HEADROOM);
setInternal(AudioSettings::SETTING_RESAMPLING_QUALITY, static_cast<int>(AudioSettings::DEFAULT_RESAMPLING_QUALITY));
setInternal(AudioSettings::SETTING_VOLUME, AudioSettings::DEFAULT_VOLUME);
// Input event options
setInternal("keymap", "");
@ -298,6 +303,10 @@ void Settings::validate()
{
string s;
int i;
float f;
f = getFloat("speed");
if (f <= 0) setInternal("speed", "1.0");
s = getString("timing");
if(s != "sleep" && s != "busy") setInternal("timing", "sleep");
@ -359,11 +368,7 @@ void Settings::validate()
if(i < 0 || i > 6) setInternal("plr.tm.horizon", 5);*/
#ifdef SOUND_SUPPORT
i = getInt("volume");
if(i < 0 || i > 100) setInternal("volume", "100");
i = getInt("freq");
if(!(i == 11025 || i == 22050 || i == 31400 || i == 44100 || i == 48000))
setInternal("freq", "31400");
AudioSettings::normalize(*this);
#endif
i = getInt("joydeadzone");
@ -438,15 +443,19 @@ void Settings::usage() const
<< " -palette <standard| Use the specified color palette\n"
<< " z26|\n"
<< " user>\n"
<< " -framerate <number> Display the given number of frames per second (0 to auto-calculate)\n"
<< " -speed <number> Run emulation at the given speed\n"
<< " -timing <sleep|busy> Use the given type of wait between frames\n"
<< " -uimessages <1|0> Show onscreen UI messages for different events\n"
<< endl
#ifdef SOUND_SUPPORT
<< " -sound <1|0> Enable sound generation\n"
<< " -fragsize <number> The size of sound fragments (must be a power of two)\n"
<< " -freq <number> Set sound sample output frequency (11025|22050|31400|44100|48000)\n"
<< " -volume <number> Set the volume (0 - 100)\n"
<< " -audio.enabled <1|0> Enable audio\n"
<< " -audio.preset <1-5> Audio preset (or 1 for custom)\n"
<< " -audio.sample_rate <number> Output sample rate (44100|48000|96000)\n"
<< " -audio.fragment_size <number> Fragment size (128|256|512|1024|2048|4096)\n"
<< " -audio.buffer_size <number> Max. number of additional half-frames to buffer (0 -- 20)\n"
<< " -audio.headroom <number> Additional half-frames to prebuffer (0 -- 20)\n"
<< " -audio.resampling_quality <1-3> Resampling quality\n"
<< " -audio.volume <number> Vokume (0 -- 100)\n"
<< endl
#endif
<< " -tia.zoom <zoom> Use the specified zoom level (windowed mode) for TIA image\n"

View File

@ -19,8 +19,9 @@
#define SOUND_HXX
class OSystem;
class AudioQueue;
class EmulationTiming;
#include "Serializable.hxx"
#include "bspf.hxx"
/**
@ -29,7 +30,7 @@ class OSystem;
@author Stephen Anthony
*/
class Sound : public Serializable
class Sound
{
public:
/**
@ -47,26 +48,11 @@ class Sound : public Serializable
*/
virtual void setEnabled(bool enable) = 0;
/**
Sets the number of channels (mono or stereo sound).
@param channels The number of channels
*/
virtual void setChannels(uInt32 channels) = 0;
/**
Sets the display framerate. Sound generation for NTSC and PAL games
depends on the framerate, so we need to set it here.
@param framerate The base framerate depending on NTSC or PAL ROM
*/
virtual void setFrameRate(float framerate) = 0;
/**
Start the sound system, initializing it if necessary. This must be
called before any calls are made to derived methods.
*/
virtual void open() = 0;
virtual void open(shared_ptr<AudioQueue>, EmulationTiming*) = 0;
/**
Should be called to stop the sound system. Once called the sound
@ -81,20 +67,21 @@ class Sound : public Serializable
*/
virtual void mute(bool state) = 0;
/**
Get the fragment size.
*/
virtual uInt32 getFragmentSize() const = 0;
/**
Get the sample rate.
*/
virtual uInt32 getSampleRate() const = 0;
/**
Reset the sound device.
*/
virtual void reset() = 0;
/**
Sets the sound register to a given value.
@param addr The register address
@param value The value to save into the register
@param cycle The system cycle at which the register is being updated
*/
virtual void set(uInt16 addr, uInt8 value, uInt64 cycle) = 0;
/**
Sets the volume of the sound device to the specified level. The
volume is given as a percentage from 0 to 100. Values outside
@ -102,7 +89,7 @@ class Sound : public Serializable
@param percent The new volume percentage level for the sound device
*/
virtual void setVolume(Int32 percent) = 0;
virtual void setVolume(uInt32 percent) = 0;
/**
Adjusts the volume of the sound device based on the given direction.

View File

@ -1,388 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "System.hxx"
#include "TIAConstants.hxx"
#include "TIASnd.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIASound::TIASound(Int32 outputFrequency)
: myChannelMode(Hardware2Stereo),
myOutputFrequency(outputFrequency),
myOutputCounter(0),
myVolumePercentage(100)
{
reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::reset()
{
// Fill the polynomials
polyInit(Bit4, 4, 4, 3);
polyInit(Bit5, 5, 5, 3);
polyInit(Bit9, 9, 9, 5);
// Initialize instance variables
for(int chan = 0; chan <= 1; ++chan)
{
myVolume[chan] = 0;
myDivNCnt[chan] = 0;
myDivNMax[chan] = 0;
myDiv3Cnt[chan] = 3;
myAUDC[chan] = 0;
myAUDF[chan] = 0;
myAUDV[chan] = 0;
myP4[chan] = 0;
myP5[chan] = 0;
myP9[chan] = 0;
}
myOutputCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::outputFrequency(Int32 freq)
{
myOutputFrequency = freq;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string TIASound::channels(uInt32 hardware, bool stereo)
{
if(hardware == 1)
myChannelMode = Hardware1;
else
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
switch(myChannelMode)
{
case Hardware1: return "Hardware1";
case Hardware2Mono: return "Hardware2Mono";
case Hardware2Stereo: return "Hardware2Stereo";
}
return EmptyString;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::set(uInt16 address, uInt8 value)
{
int chan = ~address & 0x1;
switch(address)
{
case TIARegister::AUDC0:
case TIARegister::AUDC1:
myAUDC[chan] = value & 0x0f;
break;
case TIARegister::AUDF0:
case TIARegister::AUDF1:
myAUDF[chan] = value & 0x1f;
break;
case TIARegister::AUDV0:
case TIARegister::AUDV1:
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
break;
default:
return;
}
uInt16 newVal = 0;
// An AUDC value of 0 is a special case
if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5)
{
// Indicate the clock is zero so no processing will occur,
// and set the output to the selected volume
newVal = 0;
myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100;
}
else
{
// Otherwise calculate the 'divide by N' value
newVal = myAUDF[chan] + 1;
// If bits 2 & 3 are set, then multiply the 'div by n' count by 3
if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3)
newVal *= 3;
}
// Only reset those channels that have changed
if(newVal != myDivNMax[chan])
{
// Reset the divide by n counters
myDivNMax[chan] = newVal;
// If the channel is now volume only or was volume only,
// reset the counter (otherwise let it complete the previous)
if ((myDivNCnt[chan] == 0) || (newVal == 0))
myDivNCnt[chan] = newVal;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 TIASound::get(uInt16 address) const
{
switch(address)
{
case TIARegister::AUDC0: return myAUDC[0];
case TIARegister::AUDC1: return myAUDC[1];
case TIARegister::AUDF0: return myAUDF[0];
case TIARegister::AUDF1: return myAUDF[1];
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
default: return 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::volume(uInt32 percent)
{
if(percent <= 100)
myVolumePercentage = percent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::process(Int16* buffer, uInt32 samples)
{
// Make temporary local copy
uInt8 audc0 = myAUDC[0], audc1 = myAUDC[1];
uInt8 p5_0 = myP5[0], p5_1 = myP5[1];
uInt8 div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
Int16 v0 = myVolume[0], v1 = myVolume[1];
// Take external volume into account
Int16 audv0 = (myAUDV[0] * myVolumePercentage) / 100,
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
// Loop until the sample buffer is full
while(samples > 0)
{
// Process channel 0
if (div_n_cnt0 > 1)
{
div_n_cnt0--;
}
else if (div_n_cnt0 == 1)
{
int prev_bit5 = Bit5[p5_0];
div_n_cnt0 = myDivNMax[0];
// The P5 counter has multiple uses, so we increment it here
p5_0++;
if (p5_0 == POLY5_SIZE)
p5_0 = 0;
// Check clock modifier for clock tick
if ((audc0 & 0x02) == 0 ||
((audc0 & 0x01) == 0 && Div31[p5_0]) ||
((audc0 & 0x01) == 1 && Bit5[p5_0]) ||
((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5))
{
if (audc0 & 0x04) // Pure modified clock selected
{
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
{
if ( Bit5[p5_0] != prev_bit5 )
{
myDiv3Cnt[0]--;
if ( !myDiv3Cnt[0] )
{
myDiv3Cnt[0] = 3;
v0 = v0 ? 0 : audv0;
}
}
}
else
{
// If the output was set turn it off, else turn it on
v0 = v0 ? 0 : audv0;
}
}
else if (audc0 & 0x08) // Check for p5/p9
{
if (audc0 == POLY9) // Check for poly9
{
// Increase the poly9 counter
myP9[0]++;
if (myP9[0] == POLY9_SIZE)
myP9[0] = 0;
v0 = Bit9[myP9[0]] ? audv0 : 0;
}
else if ( audc0 & 0x02 )
{
v0 = (v0 || audc0 & 0x01) ? 0 : audv0;
}
else // Must be poly5
{
v0 = Bit5[p5_0] ? audv0 : 0;
}
}
else // Poly4 is the only remaining option
{
// Increase the poly4 counter
myP4[0]++;
if (myP4[0] == POLY4_SIZE)
myP4[0] = 0;
v0 = Bit4[myP4[0]] ? audv0 : 0;
}
}
}
// Process channel 1
if (div_n_cnt1 > 1)
{
div_n_cnt1--;
}
else if (div_n_cnt1 == 1)
{
int prev_bit5 = Bit5[p5_1];
div_n_cnt1 = myDivNMax[1];
// The P5 counter has multiple uses, so we increment it here
p5_1++;
if (p5_1 == POLY5_SIZE)
p5_1 = 0;
// Check clock modifier for clock tick
if ((audc1 & 0x02) == 0 ||
((audc1 & 0x01) == 0 && Div31[p5_1]) ||
((audc1 & 0x01) == 1 && Bit5[p5_1]) ||
((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5))
{
if (audc1 & 0x04) // Pure modified clock selected
{
if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
{
if ( Bit5[p5_1] != prev_bit5 )
{
myDiv3Cnt[1]--;
if ( ! myDiv3Cnt[1] )
{
myDiv3Cnt[1] = 3;
v1 = v1 ? 0 : audv1;
}
}
}
else
{
// If the output was set turn it off, else turn it on
v1 = v1 ? 0 : audv1;
}
}
else if (audc1 & 0x08) // Check for p5/p9
{
if (audc1 == POLY9) // Check for poly9
{
// Increase the poly9 counter
myP9[1]++;
if (myP9[1] == POLY9_SIZE)
myP9[1] = 0;
v1 = Bit9[myP9[1]] ? audv1 : 0;
}
else if ( audc1 & 0x02 )
{
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
}
else // Must be poly5
{
v1 = Bit5[p5_1] ? audv1 : 0;
}
}
else // Poly4 is the only remaining option
{
// Increase the poly4 counter
myP4[1]++;
if (myP4[1] == POLY4_SIZE)
myP4[1] = 0;
v1 = Bit4[myP4[1]] ? audv1 : 0;
}
}
}
myOutputCounter += myOutputFrequency;
switch(myChannelMode)
{
case Hardware2Mono: // mono sampling with 2 hardware channels
while((samples > 0) && (myOutputCounter >= 31400))
{
Int16 byte = v0 + v1;
*(buffer++) = byte;
*(buffer++) = byte;
myOutputCounter -= 31400;
samples--;
}
break;
case Hardware2Stereo: // stereo sampling with 2 hardware channels
while((samples > 0) && (myOutputCounter >= 31400))
{
*(buffer++) = v0;
*(buffer++) = v1;
myOutputCounter -= 31400;
samples--;
}
break;
case Hardware1: // mono/stereo sampling with only 1 hardware channel
while((samples > 0) && (myOutputCounter >= 31400))
{
*(buffer++) = v0 + v1;
myOutputCounter -= 31400;
samples--;
}
break;
}
}
// Save for next round
myP5[0] = p5_0;
myP5[1] = p5_1;
myVolume[0] = v0;
myVolume[1] = v1;
myDivNCnt[0] = div_n_cnt0;
myDivNCnt[1] = div_n_cnt1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::polyInit(uInt8* poly, int size, int f0, int f1)
{
int mask = (1 << size) - 1, x = mask;
for(int i = 0; i < mask; i++)
{
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
poly[i] = x & 1;
// calculate next bit
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt8 TIASound::Div31[POLY5_SIZE] = {
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

View File

@ -1,183 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIASOUND_HXX
#define TIASOUND_HXX
#include "bspf.hxx"
/**
This class implements a fairly accurate emulation of the TIA sound
hardware. This class uses code/ideas from z26 and MESS.
Currently, the sound generation routines work at 31400Hz only.
Resampling can be done by passing in a different output frequency.
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
*/
class TIASound
{
public:
/**
Create a new TIA Sound object using the specified output frequency
*/
TIASound(Int32 outputFrequency = 31400);
public:
/**
Reset the sound emulation to its power-on state
*/
void reset();
/**
Set the frequency output samples should be generated at
*/
void outputFrequency(Int32 freq);
/**
Selects the number of audio channels per sample. There are two factors
to consider: hardware capability and desired mixing.
@param hardware The number of channels supported by the sound system
@param stereo Whether to output the internal sound signals into 1
or 2 channels
@return Status of the channel configuration used
*/
string channels(uInt32 hardware, bool stereo);
public:
/**
Sets the specified sound register to the given value
@param address Register address
@param value Value to store in the register
*/
void set(uInt16 address, uInt8 value);
/**
Gets the specified sound register's value
@param address Register address
*/
uInt8 get(uInt16 address) const;
/**
Create sound samples based on the current sound register settings
in the specified buffer. NOTE: If channels is set to stereo then
the buffer will need to be twice as long as the number of samples.
@param buffer The location to store generated samples
@param samples The number of samples to generate
*/
void process(Int16* buffer, uInt32 samples);
/**
Set the volume of the samples created (0-100)
*/
void volume(uInt32 percent);
private:
void polyInit(uInt8* poly, int size, int f0, int f1);
private:
// Definitions for AUDCx (15, 16)
enum AUDCxRegister
{
SET_TO_1 = 0x00, // 0000
POLY4 = 0x01, // 0001
DIV31_POLY4 = 0x02, // 0010
POLY5_POLY4 = 0x03, // 0011
PURE1 = 0x04, // 0100
PURE2 = 0x05, // 0101
DIV31_PURE = 0x06, // 0110
POLY5_2 = 0x07, // 0111
POLY9 = 0x08, // 1000
POLY5 = 0x09, // 1001
DIV31_POLY5 = 0x0a, // 1010
POLY5_POLY5 = 0x0b, // 1011
DIV3_PURE = 0x0c, // 1100
DIV3_PURE2 = 0x0d, // 1101
DIV93_PURE = 0x0e, // 1110
POLY5_DIV3 = 0x0f // 1111
};
enum {
POLY4_SIZE = 0x000f,
POLY5_SIZE = 0x001f,
POLY9_SIZE = 0x01ff,
DIV3_MASK = 0x0c,
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
// then another 8 for 16-bit sound
};
enum ChannelMode {
Hardware2Mono, // mono sampling with 2 hardware channels
Hardware2Stereo, // stereo sampling with 2 hardware channels
Hardware1 // mono/stereo sampling with only 1 hardware channel
};
private:
// Structures to hold the 6 tia sound control bytes
uInt8 myAUDC[2]; // AUDCx (15, 16)
uInt8 myAUDF[2]; // AUDFx (17, 18)
Int16 myAUDV[2]; // AUDVx (19, 1A)
Int16 myVolume[2]; // Last output volume for each channel
uInt8 myP4[2]; // Position pointer for the 4-bit POLY array
uInt8 myP5[2]; // Position pointer for the 5-bit POLY array
uInt16 myP9[2]; // Position pointer for the 9-bit POLY array
uInt8 myDivNCnt[2]; // Divide by n counter. one for each channel
uInt8 myDivNMax[2]; // Divide by n maximum, one for each channel
uInt8 myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode
ChannelMode myChannelMode;
Int32 myOutputFrequency;
Int32 myOutputCounter;
uInt32 myVolumePercentage;
/*
Initialize the bit patterns for the polynomials (at runtime).
The 4bit and 5bit patterns are the identical ones used in the tia chip.
Though the patterns could be packed with 8 bits per byte, using only a
single bit per byte keeps the math simple, which is important for
efficient processing.
*/
uInt8 Bit4[POLY4_SIZE];
uInt8 Bit5[POLY5_SIZE];
uInt8 Bit9[POLY9_SIZE];
/*
The 'Div by 31' counter is treated as another polynomial because of
the way it operates. It does not have a 50% duty cycle, but instead
has a 13:18 ratio (of course, 13+18 = 31). This could also be
implemented by using counters.
*/
static const uInt8 Div31[POLY5_SIZE];
private:
// Following constructors and assignment operators not supported
TIASound(const TIASound&) = delete;
TIASound(TIASound&&) = delete;
TIASound& operator=(const TIASound&) = delete;
TIASound& operator=(TIASound&&) = delete;
};
#endif

View File

@ -138,11 +138,12 @@ const FBSurface& TIASurface::baseSurface(GUI::Rect& rect) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 TIASurface::pixel(uInt32 idx, uInt8 shift)
uInt32 TIASurface::mapIndexedPixel(uInt8 indexedColor, uInt8 shift)
{
return myPalette[*(myTIA->frameBuffer() + idx) | shift];
return myPalette[indexedColor | shift];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASurface::setNTSC(NTSCFilter::Preset preset, bool show)
{

View File

@ -73,10 +73,9 @@ class TIASurface
const FBSurface& baseSurface(GUI::Rect& rect) const;
/**
Get the TIA pixel associated with the given TIA buffer index,
shifting by the given offset (for greyscale values).
*/
uInt32 pixel(uInt32 idx, uInt8 shift = 0);
Use the palette to map a single indexed pixel color. This is used by the TIA output widget.
*/
uInt32 mapIndexedPixel(uInt8 indexedColor, uInt8 shift = 0);
/**
Get the NTSCFilter object associated with the framebuffer

View File

@ -51,8 +51,11 @@ MODULE_OBJS := \
src/emucore/CompuMate.o \
src/emucore/Console.o \
src/emucore/Control.o \
src/emucore/DispatchResult.o \
src/emucore/Driving.o \
src/emucore/EventHandler.o \
src/emucore/EmulationTiming.o \
src/emucore/EmulationWorker.o \
src/emucore/FrameBuffer.o \
src/emucore/FBSurface.o \
src/emucore/FSNode.o \
@ -75,7 +78,6 @@ MODULE_OBJS := \
src/emucore/Settings.o \
src/emucore/Switches.o \
src/emucore/System.o \
src/emucore/TIASnd.o \
src/emucore/TIASurface.o \
src/emucore/Thumbulator.o

168
src/emucore/tia/Audio.cxx Normal file
View File

@ -0,0 +1,168 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "Audio.hxx"
#include "AudioQueue.hxx"
#include <cmath>
namespace {
constexpr double R_MAX = 30.;
constexpr double R = 1.;
Int16 mixingTableEntry(uInt8 v, uInt8 vMax)
{
return static_cast<Int16>(
floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v)))
);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Audio::Audio()
: myAudioQueue(nullptr),
myCurrentFragment(nullptr)
{
for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
for (uInt8 i = 0; i <= 0x0f; i++) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::reset()
{
myCounter = 0;
mySampleIndex = 0;
myChannel0.reset();
myChannel1.reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::setAudioQueue(shared_ptr<AudioQueue> queue)
{
myAudioQueue = queue;
myCurrentFragment = myAudioQueue->enqueue();
mySampleIndex = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::tick()
{
switch (myCounter) {
case 9:
case 81:
myChannel0.phase0();
myChannel1.phase0();
break;
case 37:
case 149:
phase1();
break;
}
if (++myCounter == 228) myCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::phase1()
{
uInt8 sample0 = myChannel0.phase1();
uInt8 sample1 = myChannel1.phase1();
if (!myAudioQueue) return;
if (myAudioQueue->isStereo()) {
myCurrentFragment[2*mySampleIndex] = myMixingTableIndividual[sample0];
myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1];
} else {
myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
}
if (++mySampleIndex == myAudioQueue->fragmentSize()) {
mySampleIndex = 0;
myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioChannel& Audio::channel0()
{
return myChannel0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioChannel& Audio::channel1()
{
return myChannel1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Audio::name() const
{
return "TIA_Audio";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Audio::save(Serializer& out) const
{
try
{
out.putString(name());
out.putInt(myCounter);
// The queue starts out pristine after loading, so we don't need to save
// any other parts of our state
if (!myChannel0.save(out)) return false;
if (!myChannel1.save(out)) return false;
}
catch(...)
{
cerr << "ERROR: TIA_Audio::save" << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Audio::load(Serializer& in)
{
try
{
if (in.getString() != name()) return false;
myCounter = in.getInt();
if (!myChannel0.load(in)) return false;
if (!myChannel1.load(in)) return false;
}
catch(...)
{
cerr << "ERROR: TIA_Audio::load" << endl;
return false;
}
return true;
}

73
src/emucore/tia/Audio.hxx Normal file
View File

@ -0,0 +1,73 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIA_AUDIO_HXX
#define TIA_AUDIO_HXX
#include "bspf.hxx"
#include "AudioChannel.hxx"
#include "Serializable.hxx"
class AudioQueue;
class Audio : public Serializable
{
public:
Audio();
void reset();
void setAudioQueue(shared_ptr<AudioQueue> queue);
void tick();
AudioChannel& channel0();
AudioChannel& channel1();
/**
Serializable methods (see that class for more information).
*/
bool save(Serializer& out) const override;
bool load(Serializer& in) override;
string name() const override;
private:
void phase1();
private:
shared_ptr<AudioQueue> myAudioQueue;
uInt8 myCounter;
AudioChannel myChannel0;
AudioChannel myChannel1;
Int16 myMixingTableSum[0x1e + 1];
Int16 myMixingTableIndividual[0x0f + 1];
Int16* myCurrentFragment;
uInt32 mySampleIndex;
private:
Audio(const Audio&) = delete;
Audio(Audio&&) = delete;
Audio& operator=(const Audio&) = delete;
Audio& operator=(Audio&&) = delete;
};
#endif // TIA_AUDIO_HXX

View File

@ -0,0 +1,207 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AudioChannel.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioChannel::AudioChannel()
{
reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::reset()
{
myAudc = myAudv = myAudf = 0;
myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false;
myDivCounter = myPulseCounter = myNoiseCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::phase0()
{
if (myClockEnable) {
myNoiseCounterBit4 = myNoiseCounter & 0x01;
switch (myAudc & 0x03) {
case 0x00:
case 0x01:
myPulseCounterHold = false;
break;
case 0x02:
myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02;
break;
case 0x03:
myPulseCounterHold = !myNoiseCounterBit4;
break;
}
switch (myAudc & 0x03) {
case 0x00:
myNoiseFeedback =
((myPulseCounter ^ myNoiseCounter) & 0x01) ||
!(myNoiseCounter || (myPulseCounter != 0x0a)) ||
!(myAudc & 0x0c);
break;
default:
myNoiseFeedback =
((myNoiseCounter & 0x04 ? 1 : 0) ^ (myNoiseCounter & 0x01)) ||
myNoiseCounter == 0;
break;
}
}
myClockEnable = myDivCounter == myAudf;
if (myDivCounter == myAudf || myDivCounter == 0x1f) {
myDivCounter = 0;
} else {
myDivCounter++;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 AudioChannel::phase1()
{
bool pulseFeedback = false;
if (myClockEnable) {
switch (myAudc >> 2) {
case 0x00:
pulseFeedback =
((myPulseCounter & 0x02 ? 1 : 0) ^ (myPulseCounter & 0x01)) &&
(myPulseCounter != 0x0a) &&
(myAudc & 0x03);
break;
case 0x01:
pulseFeedback = !(myPulseCounter & 0x08);
break;
case 0x02:
pulseFeedback = !myNoiseCounterBit4;
break;
case 0x03:
pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e));
break;
}
myNoiseCounter >>= 1;
if (myNoiseFeedback) {
myNoiseCounter |= 0x10;
}
if (!myPulseCounterHold) {
myPulseCounter = ~(myPulseCounter >> 1) & 0x07;
if (pulseFeedback) {
myPulseCounter |= 0x08;
}
}
}
return (myPulseCounter & 0x01) * myAudv;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audc(uInt8 value)
{
myAudc = value & 0x0f;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audv(uInt8 value)
{
myAudv = value & 0x0f;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioChannel::audf(uInt8 value)
{
myAudf = value & 0x1f;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string AudioChannel::name() const
{
return "TIA_AudioChannel";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioChannel::save(Serializer& out) const
{
try
{
out.putString(name());
out.putInt(myAudc);
out.putInt(myAudv);
out.putInt(myAudf);
out.putBool(myClockEnable);
out.putBool(myNoiseFeedback);
out.putBool(myNoiseCounterBit4);
out.putBool(myPulseCounterHold);
out.putInt(myDivCounter);
out.putInt(myPulseCounter);
out.putInt(myNoiseCounter);
}
catch(...)
{
cerr << "ERROR: TIA_AudioChannel::save" << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioChannel::load(Serializer& in)
{
try
{
if (in.getString() != name()) return false;
myAudc = in.getInt();
myAudv = in.getInt();
myAudf = in.getInt();
myClockEnable = in.getBool();
myNoiseFeedback = in.getBool();
myNoiseCounterBit4 = in.getBool();
myPulseCounterHold = in.getBool();
myDivCounter = in.getInt();
myPulseCounter = in.getInt();
myNoiseCounter = in.getInt();
}
catch(...)
{
cerr << "ERROR: TIA_AudioChannel::load" << endl;
return false;
}
return true;
}

View File

@ -0,0 +1,69 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIA_AUDIO_CHANNEL_HXX
#define TIA_AUDIO_CHANNEL_HXX
#include "bspf.hxx"
#include "Serializable.hxx"
class AudioChannel : public Serializable
{
public:
AudioChannel();
void reset();
void phase0();
uInt8 phase1();
void audc(uInt8 value);
void audf(uInt8 value);
void audv(uInt8 value);
/**
Serializable methods (see that class for more information).
*/
bool save(Serializer& out) const override;
bool load(Serializer& in) override;
string name() const override;
private:
uInt8 myAudc;
uInt8 myAudv;
uInt8 myAudf;
bool myClockEnable;
bool myNoiseFeedback;
bool myNoiseCounterBit4;
bool myPulseCounterHold;
uInt8 myDivCounter;
uInt8 myPulseCounter;
uInt8 myNoiseCounter;
private:
AudioChannel(const AudioChannel&);
AudioChannel(AudioChannel&&);
AudioChannel& operator=(const AudioChannel&);
AudioChannel& operator=(AudioChannel&&);
};
#endif // TIA_AUDIO_CHANNEL_HXX

View File

@ -26,7 +26,7 @@ PaddleReader::PaddleReader()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaddleReader::reset(double timestamp)
void PaddleReader::reset(uInt64 timestamp)
{
myU = 0;
myIsDumped = false;
@ -38,7 +38,7 @@ void PaddleReader::reset(double timestamp)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaddleReader::vblank(uInt8 value, double timestamp)
void PaddleReader::vblank(uInt8 value, uInt64 timestamp)
{
bool oldIsDumped = myIsDumped;
@ -53,7 +53,7 @@ void PaddleReader::vblank(uInt8 value, double timestamp)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 PaddleReader::inpt(double timestamp)
uInt8 PaddleReader::inpt(uInt64 timestamp)
{
updateCharge(timestamp);
@ -63,7 +63,7 @@ uInt8 PaddleReader::inpt(double timestamp)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaddleReader::update(double value, double timestamp, ConsoleTiming consoleTiming)
void PaddleReader::update(double value, uInt64 timestamp, ConsoleTiming consoleTiming)
{
if (consoleTiming != myConsoleTiming) {
setConsoleTiming(consoleTiming);
@ -94,13 +94,13 @@ void PaddleReader::setConsoleTiming(ConsoleTiming consoleTiming)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaddleReader::updateCharge(double timestamp)
void PaddleReader::updateCharge(uInt64 timestamp)
{
if (myIsDumped) return;
if (myValue >= 0)
myU = USUPP * (1 - (1 - myU / USUPP) *
exp(-(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq));
exp(-static_cast<double>(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq));
myTimestamp = timestamp;
}

View File

@ -30,14 +30,14 @@ class PaddleReader : public Serializable
public:
void reset(double timestamp);
void reset(uInt64 timestamp);
void vblank(uInt8 value, double timestamp);
void vblank(uInt8 value, uInt64 timestamp);
bool vblankDumped() const { return myIsDumped; }
uInt8 inpt(double timestamp);
uInt8 inpt(uInt64 timestamp);
void update(double value, double timestamp, ConsoleTiming consoleTiming);
void update(double value, uInt64 timestamp, ConsoleTiming consoleTiming);
/**
Serializable methods (see that class for more information).
@ -50,7 +50,7 @@ class PaddleReader : public Serializable
void setConsoleTiming(ConsoleTiming timing);
void updateCharge(double timestamp);
void updateCharge(uInt64 timestamp);
private:
@ -58,7 +58,7 @@ class PaddleReader : public Serializable
double myU;
double myValue;
double myTimestamp;
uInt64 myTimestamp;
ConsoleTiming myConsoleTiming;
double myClockFreq;

View File

@ -23,6 +23,8 @@
#include "DelayQueueIteratorImpl.hxx"
#include "TIAConstants.hxx"
#include "frame-manager/FrameManager.hxx"
#include "AudioQueue.hxx"
#include "DispatchResult.hxx"
#ifdef DEBUGGER_SUPPORT
#include "CartDebug.hxx"
@ -65,9 +67,8 @@ enum ResxCounter: uInt8 {
static constexpr uInt8 resxLateHblankThreshold = 73;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA::TIA(Console& console, Sound& sound, Settings& settings)
TIA::TIA(Console& console, Settings& settings)
: myConsole(console),
mySound(sound),
mySettings(settings),
myFrameManager(nullptr),
myPlayfield(~CollisionMask::playfield & 0x7FFF),
@ -116,6 +117,12 @@ void TIA::setFrameManager(AbstractFrameManager *frameManager)
myFrameManager->setJitterFactor(myJitterFactor);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::setAudioQueue(shared_ptr<AudioQueue> queue)
{
myAudio.setAudioQueue(queue);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::clearFrameManager()
{
@ -138,7 +145,6 @@ void TIA::reset()
myCollisionMask = 0;
myLinesSinceChange = 0;
myCollisionUpdateRequired = false;
myAutoFrameEnabled = false;
myColorLossEnabled = myColorLossActive = false;
myColorHBlank = 0;
myLastCycle = 0;
@ -159,11 +165,12 @@ void TIA::reset()
myInput0.reset();
myInput1.reset();
myAudio.reset();
myTimestamp = 0;
for (PaddleReader& paddleReader : myPaddleReaders)
paddleReader.reset(myTimestamp);
mySound.reset();
myDelayQueue.reset();
myCyclesAtFrameStart = 0;
@ -174,6 +181,11 @@ void TIA::reset()
frameReset(); // Recalculate the size of the display
}
myFrontBufferFrameRate = myFrameBufferFrameRate = 0;
myFrontBufferScanlines = myFrameBufferScanlines = 0;
myNewFramePending = false;
// Must be done last, after all other items have reset
enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors"));
setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
@ -186,8 +198,9 @@ void TIA::reset()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::frameReset()
{
memset(myBackBuffer, 0, 160 * TIAConstants::frameBufferHeight);
memset(myFrontBuffer, 0, 160 * TIAConstants::frameBufferHeight);
memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight);
myAutoFrameEnabled = mySettings.getInt("framerate") <= 0;
enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss");
}
@ -227,8 +240,6 @@ bool TIA::save(Serializer& out) const
{
out.putString(name());
if(!mySound.save(out)) return false;
if(!myDelayQueue.save(out)) return false;
if(!myFrameManager->save(out)) return false;
@ -239,6 +250,7 @@ bool TIA::save(Serializer& out) const
if(!myPlayer0.save(out)) return false;
if(!myPlayer1.save(out)) return false;
if(!myBall.save(out)) return false;
if(!myAudio.save(out)) return false;
for (const PaddleReader& paddleReader : myPaddleReaders)
if(!paddleReader.save(out)) return false;
@ -275,11 +287,14 @@ bool TIA::save(Serializer& out) const
out.putLong(myTimestamp);
out.putBool(myAutoFrameEnabled);
out.putByteArray(myShadowRegisters, 64);
out.putLong(myCyclesAtFrameStart);
out.putInt(myFrameBufferScanlines);
out.putInt(myFrontBufferScanlines);
out.putDouble(myFrameBufferFrameRate);
out.putDouble(myFrontBufferFrameRate);
}
catch(...)
{
@ -298,8 +313,6 @@ bool TIA::load(Serializer& in)
if(in.getString() != name())
return false;
if(!mySound.load(in)) return false;
if(!myDelayQueue.load(in)) return false;
if(!myFrameManager->load(in)) return false;
@ -310,6 +323,7 @@ bool TIA::load(Serializer& in)
if(!myPlayer0.load(in)) return false;
if(!myPlayer1.load(in)) return false;
if(!myBall.load(in)) return false;
if(!myAudio.load(in)) return false;
for (PaddleReader& paddleReader : myPaddleReaders)
if(!paddleReader.load(in)) return false;
@ -346,11 +360,14 @@ bool TIA::load(Serializer& in)
myTimestamp = in.getLong();
myAutoFrameEnabled = in.getBool();
in.getByteArray(myShadowRegisters, 64);
myCyclesAtFrameStart = in.getLong();
myFrameBufferScanlines = in.getInt();
myFrontBufferScanlines = in.getInt();
myFrameBufferFrameRate = in.getDouble();
myFrontBufferFrameRate = in.getDouble();
}
catch(...)
{
@ -521,33 +538,35 @@ bool TIA::poke(uInt16 address, uInt8 value)
break;
////////////////////////////////////////////////////////////
// FIXME - rework this when we add the new sound core
case AUDV0:
mySound.set(address, value, mySystem->cycles());
myAudio.channel0().audv(value);
myShadowRegisters[address] = value;
break;
case AUDV1:
mySound.set(address, value, mySystem->cycles());
myAudio.channel1().audv(value);
myShadowRegisters[address] = value;
break;
case AUDF0:
mySound.set(address, value, mySystem->cycles());
myAudio.channel0().audf(value);
myShadowRegisters[address] = value;
break;
case AUDF1:
mySound.set(address, value, mySystem->cycles());
myAudio.channel1().audf(value);
myShadowRegisters[address] = value;
break;
case AUDC0:
mySound.set(address, value, mySystem->cycles());
myAudio.channel0().audc(value);
myShadowRegisters[address] = value;
break;
case AUDC1:
mySound.set(address, value, mySystem->cycles());
myAudio.channel1().audc(value);
myShadowRegisters[address] = value;
break;
////////////////////////////////////////////////////////////
case HMOVE:
myDelayQueue.push(HMOVE, value, Delay::hmove);
@ -777,7 +796,10 @@ bool TIA::saveDisplay(Serializer& out) const
{
try
{
out.putByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
out.putByteArray(myFramebuffer, 160* TIAConstants::frameBufferHeight);
out.putByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
out.putByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
out.putBool(myNewFramePending);
}
catch(...)
{
@ -795,6 +817,9 @@ bool TIA::loadDisplay(Serializer& in)
{
// Reset frame buffer pointer and data
in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
in.getByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
myNewFramePending = in.getBool();
}
catch(...)
{
@ -806,9 +831,30 @@ bool TIA::loadDisplay(Serializer& in)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::update()
void TIA::update(DispatchResult& result, uInt32 maxCycles)
{
mySystem->m6502().execute(25000);
mySystem->m6502().execute(maxCycles, result);
updateEmulation();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::renderToFrameBuffer()
{
if (!myNewFramePending) return;
memcpy(myFramebuffer, myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
myFrameBufferFrameRate = myFrontBufferFrameRate;
myFrameBufferScanlines = myFrontBufferScanlines;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::update(uInt32 maxCycles)
{
DispatchResult dispatchResult;
update(dispatchResult, maxCycles);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1143,16 +1189,19 @@ void TIA::onFrameComplete()
myCyclesAtFrameStart = mySystem->cycles();
if (myXAtRenderingStart > 0)
memset(myFramebuffer, 0, myXAtRenderingStart);
memset(myBackBuffer, 0, myXAtRenderingStart);
// Blank out any extra lines not drawn this frame
const Int32 missingScanlines = myFrameManager->missingScanlines();
if (missingScanlines > 0)
memset(myFramebuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160);
memset(myBackBuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160);
// Recalculate framerate, attempting to auto-correct for scanline 'jumps'
if(myAutoFrameEnabled)
myConsole.setFramerate(myFrameManager->frameRate());
memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight);
myFrontBufferFrameRate = frameRate();
myFrontBufferScanlines = scanlinesLastFrame();
myNewFramePending = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1188,6 +1237,8 @@ void TIA::cycle(uInt32 colorClocks)
if (++myHctr >= 228)
nextLine();
myAudio.tick();
myTimestamp++;
}
}
@ -1261,7 +1312,7 @@ void TIA::applyRsync()
myHctrDelta = 225 - myHctr;
if (myFrameManager->isRendering())
memset(myFramebuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
memset(myBackBuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
myHctr = 225;
}
@ -1300,7 +1351,7 @@ void TIA::cloneLastLine()
if (!myFrameManager->isRendering() || y == 0) return;
uInt8* buffer = myFramebuffer;
uInt8* buffer = myBackBuffer;
memcpy(buffer + y * 160, buffer + (y-1) * 160, 160);
}
@ -1373,7 +1424,7 @@ void TIA::renderPixel(uInt32 x, uInt32 y)
}
}
myFramebuffer[y * 160 + x] = color;
myBackBuffer[y * 160 + x] = color;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1399,7 +1450,7 @@ void TIA::flushLineCache()
void TIA::clearHmoveComb()
{
if (myFrameManager->isRendering() && myHstate == HState::blank)
memset(myFramebuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
memset(myBackBuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -20,7 +20,6 @@
#include "bspf.hxx"
#include "Console.hxx"
#include "Sound.hxx"
#include "Settings.hxx"
#include "Device.hxx"
#include "Serializer.hxx"
@ -29,6 +28,7 @@
#include "DelayQueueIterator.hxx"
#include "frame-manager/AbstractFrameManager.hxx"
#include "FrameLayout.hxx"
#include "Audio.hxx"
#include "Background.hxx"
#include "Playfield.hxx"
#include "Missile.hxx"
@ -40,6 +40,9 @@
#include "Control.hxx"
#include "System.hxx"
class AudioQueue;
class DispatchResult;
/**
This class is a device that emulates the Television Interface Adaptor
found in the Atari 2600 and 7800 consoles. The Television Interface
@ -96,21 +99,26 @@ class TIA : public Device
Create a new TIA for the specified console
@param console The console the TIA is associated with
@param sound The sound object the TIA is associated with
@param settings The settings object for this TIA device
*/
TIA(Console& console, Sound& sound, Settings& settings);
TIA(Console& console, Settings& settings);
virtual ~TIA() = default;
public:
/**
* Configure the frame manager.
Configure the frame manager.
*/
void setFrameManager(AbstractFrameManager *frameManager);
/**
* Clear the configured frame manager and deteach the lifecycle callbacks.
Set the audio queue. This needs to be dynamic as the queue is created after
the timing has been determined.
*/
void setAudioQueue(shared_ptr<AudioQueue> audioQueue);
/**
Clear the configured frame manager and deteach the lifecycle callbacks.
*/
void clearFrameManager();
@ -192,7 +200,25 @@ class TIA : public Device
desired frame rate to update the TIA. Invoking this method will update
the graphics buffer and generate the corresponding audio samples.
*/
void update();
void update(DispatchResult& result, uInt32 maxCycles = 50000);
void update(uInt32 maxCycles = 50000);
/**
Did we generate a new frame?
*/
bool newFramePending() { return myNewFramePending; }
/**
Render the pending frame to the framebuffer and clear the flag.
*/
void renderToFrameBuffer();
/**
Return the buffer that holds the currently drawing TIA frame
(the TIA output widget needs this).
*/
uInt8* outputBuffer() { return myBackBuffer; }
/**
Returns a pointer to the internal frame buffer.
@ -222,13 +248,12 @@ class TIA : public Device
*/
ConsoleTiming consoleTiming() const { return myConsole.timing(); }
/**
Enables/disables auto-frame calculation. If enabled, the TIA
re-adjusts the framerate at regular intervals.
float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; }
@param enabled Whether to enable or disable all auto-frame calculation
*/
void enableAutoFrame(bool enabled) { myAutoFrameEnabled = enabled; }
/**
The same, but for the frame in the frame buffer.
*/
float frameBufferFrameRate() const { return myFrameBufferFrameRate; }
/**
Enables/disables color-loss for PAL modes only.
@ -275,6 +300,11 @@ class TIA : public Device
*/
uInt32 scanlinesLastFrame() const { return myFrameManager->scanlinesLastFrame(); }
/**
The same, but for the frame in the frame buffer.
*/
uInt32 frameBufferScanlinesLastFrame() const { return myFrameBufferScanlines; }
/**
Answers the total system cycles from the start of the emulation.
*/
@ -604,7 +634,6 @@ class TIA : public Device
private:
Console& myConsole;
Sound& mySound;
Settings& mySettings;
/**
@ -641,6 +670,8 @@ class TIA : public Device
Player myPlayer1;
Ball myBall;
Audio myAudio;
/**
* The paddle readout circuits.
*/
@ -655,6 +686,19 @@ class TIA : public Device
// Pointer to the internal color-index-based frame buffer
uInt8 myFramebuffer[160 * TIAConstants::frameBufferHeight];
// The frame is rendered to the backbuffer and only copied to the framebuffer
// upon completion
uInt8 myBackBuffer[160 * TIAConstants::frameBufferHeight];
uInt8 myFrontBuffer[160 * TIAConstants::frameBufferHeight];
// We snapshot frame statistics when the back buffer is copied to the front buffer
// and when the front buffer is copied to the frame buffer
uInt32 myFrontBufferScanlines, myFrameBufferScanlines;
float myFrontBufferFrameRate, myFrameBufferFrameRate;
// Did we emit a frame?
bool myNewFramePending;
/**
* Setting this to true injects random values into undefined reads.
*/
@ -740,11 +784,9 @@ class TIA : public Device
uInt8 myColorHBlank;
/**
* The total number of color clocks since emulation started. This is a
* double a) to avoid overflows and b) as it will enter floating point
* expressions in the paddle readout simulation anyway.
* The total number of color clocks since emulation started.
*/
double myTimestamp;
uInt64 myTimestamp;
/**
* The "shadow registers" track the last written register value for the
@ -752,11 +794,6 @@ class TIA : public Device
*/
uInt8 myShadowRegisters[64];
/**
* Automatic framerate correction based on number of scanlines.
*/
bool myAutoFrameEnabled;
/**
* Indicates if color loss should be enabled or disabled. Color loss
* occurs on PAL-like systems when the previous frame contains an odd

View File

@ -47,9 +47,7 @@ FrameManager::FrameManager()
myHeight(0),
myFixedHeight(0),
myYStart(0),
myJitterEnabled(false),
myStableFrameLines(-1),
myStableFrameHeightCountdown(0)
myJitterEnabled(false)
{
onLayoutChange();
}
@ -63,9 +61,6 @@ void FrameManager::onReset()
myVsyncLines = 0;
myY = 0;
myStableFrameLines = -1;
myStableFrameHeightCountdown = 0;
myJitterEmulation.reset();
}
@ -230,9 +225,6 @@ bool FrameManager::onSave(Serializer& out) const
out.putBool(myJitterEnabled);
out.putInt(myStableFrameLines);
out.putInt(myStableFrameHeightCountdown);
return true;
}
@ -257,8 +249,5 @@ bool FrameManager::onLoad(Serializer& in)
myJitterEnabled = in.getBool();
myStableFrameLines = in.getInt();
myStableFrameHeightCountdown = in.getInt();
return true;
}

View File

@ -100,9 +100,6 @@ class FrameManager: public AbstractFrameManager {
bool myJitterEnabled;
Int32 myStableFrameLines;
uInt8 myStableFrameHeightCountdown;
JitterEmulation myJitterEmulation;
private:

View File

@ -9,7 +9,9 @@ MODULE_OBJS := \
src/emucore/tia/Ball.o \
src/emucore/tia/Background.o \
src/emucore/tia/LatchedInput.o \
src/emucore/tia/PaddleReader.o
src/emucore/tia/PaddleReader.o \
src/emucore/tia/Audio.o \
src/emucore/tia/AudioChannel.o
MODULE_DIRS += \
src/emucore/tia

View File

@ -29,6 +29,7 @@
#include "Settings.hxx"
#include "Sound.hxx"
#include "Widget.hxx"
#include "AudioSettings.hxx"
#include "AudioDialog.hxx"
@ -44,14 +45,14 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
fontWidth = font.getMaxCharWidth(),
fontHeight = font.getFontHeight();
int xpos, ypos;
int lwidth = font.getStringWidth("Sample Size (*) "),
int lwidth = font.getStringWidth("Resampling quality "),
pwidth = font.getStringWidth("512 bytes");
WidgetArray wid;
VariantList items;
// Set real dimensions
_w = 35 * fontWidth + HBORDER * 2;
_h = 7 * (lineHeight + 4) + VBORDER + _th;
_w = 45 * fontWidth + HBORDER * 2;
_h = 11 * (lineHeight + 4) + VBORDER + _th;
xpos = HBORDER; ypos = VBORDER + _th;
@ -64,43 +65,80 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
xpos += INDENT;
// Volume
myVolumeSlider = new SliderWidget(this, font, xpos, ypos, 11 * fontWidth + 5, lineHeight,
"Volume ", lwidth, 0, 4 * fontWidth, "%");
myVolumeSlider = new SliderWidget(this, font, xpos, ypos,
"Volume ", 0, 0, 4 * fontWidth, "%");
myVolumeSlider->setMinValue(1); myVolumeSlider->setMaxValue(100);
wid.push_back(myVolumeSlider);
ypos += lineHeight + 4;
//
VarList::push_back(items, "Low quality, medium lag", static_cast<int>(AudioSettings::Preset::lowQualityMediumLag));
VarList::push_back(items, "High quality, medium lag", static_cast<int>(AudioSettings::Preset::highQualityMediumLag));
VarList::push_back(items, "High quality, low lag", static_cast<int>(AudioSettings::Preset::highQualityLowLag));
VarList::push_back(items, "Ultra quality, minimal lag", static_cast<int>(AudioSettings::Preset::veryHighQualityVeryLowLag));
VarList::push_back(items, "Custom", static_cast<int>(AudioSettings::Preset::custom));
myModePopup = new PopUpWidget(this, font, xpos, ypos,
font.getStringWidth("Ultry quality, minimal lag "), lineHeight,
items, "Mode (*) ", 0, kModeChanged);
wid.push_back(myModePopup);
ypos += lineHeight + 4;
xpos += INDENT;
// Fragment size
VarList::push_back(items, "128 bytes", "128");
VarList::push_back(items, "256 bytes", "256");
VarList::push_back(items, "512 bytes", "512");
VarList::push_back(items, "1 KB", "1024");
VarList::push_back(items, "2 KB", "2048");
VarList::push_back(items, "4 KB", "4096");
items.clear();
VarList::push_back(items, "128 bytes", 128);
VarList::push_back(items, "256 bytes", 256);
VarList::push_back(items, "512 bytes", 512);
VarList::push_back(items, "1 KB", 1024);
VarList::push_back(items, "2 KB", 2048);
VarList::push_back(items, "4 KB", 4096);
myFragsizePopup = new PopUpWidget(this, font, xpos, ypos,
pwidth, lineHeight,
items, "Sample size (*) ", lwidth);
items, "Fragment size (*) ", lwidth);
wid.push_back(myFragsizePopup);
ypos += lineHeight + 4;
// Output frequency
items.clear();
VarList::push_back(items, "11025 Hz", "11025");
VarList::push_back(items, "22050 Hz", "22050");
VarList::push_back(items, "31400 Hz", "31400");
VarList::push_back(items, "44100 Hz", "44100");
VarList::push_back(items, "48000 Hz", "48000");
VarList::push_back(items, "44100 Hz", 44100);
VarList::push_back(items, "48000 Hz", 48000);
VarList::push_back(items, "96000 Hz", 96000);
myFreqPopup = new PopUpWidget(this, font, xpos, ypos,
pwidth, lineHeight,
items, "Frequency (*) ", lwidth);
items, "Sample rate (*) ", lwidth);
wid.push_back(myFreqPopup);
ypos += lineHeight + 4;
// Resampling quality
items.clear();
VarList::push_back(items, "Low", static_cast<int>(AudioSettings::ResamplingQuality::nearestNeightbour));
VarList::push_back(items, "High", static_cast<int>(AudioSettings::ResamplingQuality::lanczos_2));
VarList::push_back(items, "Ultra", static_cast<int>(AudioSettings::ResamplingQuality::lanczos_3));
myResamplingPopup = new PopUpWidget(this, font, xpos, ypos,
pwidth, lineHeight,
items, "Resampling quality ", lwidth);
wid.push_back(myResamplingPopup);
ypos += lineHeight + 4;
// Param 1
myHeadroomSlider = new SliderWidget(this, font, xpos, ypos,
"Headroom ", 0, 0, 2 * fontWidth);
myHeadroomSlider->setMinValue(1); myHeadroomSlider->setMaxValue(AudioSettings::MAX_HEADROOM);
wid.push_back(myHeadroomSlider);
ypos += lineHeight + 4;
// Param 2
myBufferSizeSlider = new SliderWidget(this, font, xpos, ypos,
"Buffer size ", 0, 0, 2 * fontWidth);
myBufferSizeSlider->setMinValue(1); myBufferSizeSlider->setMaxValue(AudioSettings::MAX_BUFFER_SIZE);
wid.push_back(myBufferSizeSlider);
// Add message concerning usage
ypos = _h - fontHeight * 2 - 24;
const GUI::Font& infofont = instance().frameBuffer().infoFont();
new StaticTextWidget(this, infofont, HBORDER, ypos,
new StaticTextWidget(this, infofont, HBORDER, ypos, "(*) Requires application restart");/* ,
font.getStringWidth("(*) Requires application restart"), fontHeight,
"(*) Requires application restart", TextAlign::Left);
"(*) Requires application restart", TextAlign::Left);*/
// Add Defaults, OK and Cancel buttons
addDefaultsOKCancelBGroup(wid, font);
@ -111,41 +149,69 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::loadConfig()
{
AudioSettings& audioSettings = instance().audioSettings();
// Volume
myVolumeSlider->setValue(instance().settings().getInt("volume"));
// Fragsize
myFragsizePopup->setSelected(instance().settings().getString("fragsize"), "512");
// Output frequency
myFreqPopup->setSelected(instance().settings().getString("freq"), "31400");
myVolumeSlider->setValue(audioSettings.volume());
// Enable sound
bool b = instance().settings().getBool("sound");
mySoundEnableCheckbox->setState(b);
mySoundEnableCheckbox->setState(audioSettings.enabled());
// Preset / mode
myModePopup->setSelected(static_cast<int>(audioSettings.preset()));
updatePresetSettings(instance().audioSettings());
// Make sure that mutually-exclusive items are not enabled at the same time
handleSoundEnableChange(b);
handleSoundEnableChange(audioSettings.enabled());
handleModeChange(audioSettings.enabled());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::updatePresetSettings(AudioSettings& audioSettings)
{
// Fragsize
myFragsizePopup->setSelected(audioSettings.fragmentSize());
// Output frequency
myFreqPopup->setSelected(audioSettings.sampleRate());
// Headroom
myHeadroomSlider->setValue(audioSettings.headroom());
// Buffer size
myBufferSizeSlider->setValue(audioSettings.bufferSize());
// Resampling quality
myResamplingPopup->setSelected(static_cast<int>(audioSettings.resamplingQuality()));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::saveConfig()
{
Settings& settings = instance().settings();
AudioSettings audioSettings = instance().audioSettings();
// Volume
settings.setValue("volume", myVolumeSlider->getValue());
audioSettings.setVolume(myVolumeSlider->getValue());
instance().sound().setVolume(myVolumeSlider->getValue());
// Fragsize
settings.setValue("fragsize", myFragsizePopup->getSelectedTag().toString());
// Output frequency
settings.setValue("freq", myFreqPopup->getSelectedTag().toString());
// Enable/disable sound (requires a restart to take effect)
audioSettings.setEnabled(mySoundEnableCheckbox->getState());
instance().sound().setEnabled(mySoundEnableCheckbox->getState());
AudioSettings::Preset preset = static_cast<AudioSettings::Preset>(myModePopup->getSelectedTag().toInt());
audioSettings.setPreset(preset);
if (preset == AudioSettings::Preset::custom) {
// Fragsize
audioSettings.setFragmentSize(myFragsizePopup->getSelectedTag().toInt());
audioSettings.setSampleRate(myFreqPopup->getSelectedTag().toInt());
audioSettings.setHeadroom(myHeadroomSlider->getValue());
audioSettings.setBufferSize(myBufferSizeSlider->getValue());
audioSettings.setResamplingQuality(static_cast<AudioSettings::ResamplingQuality>(myResamplingPopup->getSelectedTag().toInt()));
}
// Only force a re-initialization when necessary, since it can
// be a time-consuming operation
if(instance().hasConsole())
@ -172,10 +238,33 @@ void AudioDialog::setDefaults()
void AudioDialog::handleSoundEnableChange(bool active)
{
myVolumeSlider->setEnabled(active);
myFragsizePopup->setEnabled(active);
myFreqPopup->setEnabled(active);
myModePopup->setEnabled(active);
handleModeChange(active);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::handleModeChange(bool active)
{
AudioSettings::Preset preset = static_cast<AudioSettings::Preset>(myModePopup->getSelectedTag().toInt());
AudioSettings audioSettings = instance().audioSettings();
audioSettings.setPersistent(false);
audioSettings.setPreset(preset);
(cout << "Preset: " << static_cast<int>(preset) << std::endl).flush();
updatePresetSettings(audioSettings);
bool userMode = active && preset == AudioSettings::Preset::custom;
myFragsizePopup->setEnabled(userMode);
myFreqPopup->setEnabled(userMode);
myResamplingPopup->setEnabled(userMode);
myHeadroomSlider->setEnabled(userMode);
myBufferSizeSlider->setEnabled(userMode);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::handleCommand(CommandSender* sender, int cmd,
int data, int id)
@ -195,6 +284,10 @@ void AudioDialog::handleCommand(CommandSender* sender, int cmd,
handleSoundEnableChange(data == 1);
break;
case kModeChanged:
handleModeChange(true);
break;
default:
Dialog::handleCommand(sender, cmd, data, 0);
break;

View File

@ -26,6 +26,7 @@ class SliderWidget;
class StaticTextWidget;
class CheckboxWidget;
class OSystem;
class AudioSettings;
#include "bspf.hxx"
@ -41,17 +42,27 @@ class AudioDialog : public Dialog
void setDefaults() override;
void handleSoundEnableChange(bool active);
void handleModeChange(bool active);
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
private:
enum {
kSoundEnableChanged = 'ADse'
kSoundEnableChanged = 'ADse',
kModeChanged = 'ADmc'
};
CheckboxWidget* mySoundEnableCheckbox;
SliderWidget* myVolumeSlider;
PopUpWidget* myModePopup;
PopUpWidget* myFragsizePopup;
PopUpWidget* myFreqPopup;
CheckboxWidget* mySoundEnableCheckbox;
PopUpWidget* myResamplingPopup;
SliderWidget* myHeadroomSlider;
SliderWidget* myBufferSizeSlider;
private:
void updatePresetSettings(AudioSettings&);
private:
// Following constructors and assignment operators not supported

View File

@ -795,7 +795,7 @@ bool Dialog::getResizableBounds(uInt32& w, uInt32& h) const
if(instance().hasConsole())
{
ntsc = instance().console().about().InitialFrameRate == "60";
ntsc = instance().console().tia().frameLayout() == FrameLayout::ntsc;
}
uInt32 aspect = instance().settings().getInt(ntsc ?"tia.aspectn" : "tia.aspectp");

View File

@ -15,6 +15,8 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#include "bspf.hxx"
#include "Control.hxx"
#include "Dialog.hxx"
@ -32,6 +34,45 @@
#include "TIASurface.hxx"
#include "VideoDialog.hxx"
namespace {
// Emulation speed is a positive float that multiplies the framerate. However, the UI controls
// adjust speed in terms of a speedup factor (1/10, 1/9 .. 1/2, 1, 2, 3, .., 10). The following
// mapping and formatting functions implement this conversion. The speedup factor is represented
// by an integer value between -900 and 900 (0 means no speedup).
constexpr int MAX_SPEED = 900;
constexpr int MIN_SPEED = -900;
constexpr int SPEED_STEP = 10;
int mapSpeed(float speed)
{
speed = abs(speed);
return BSPF::clamp(
static_cast<int>(round(100 * (speed >= 1 ? speed - 1 : -1 / speed + 1))),
MIN_SPEED, MAX_SPEED
);
}
float unmapSpeed(int speed)
{
float f_speed = static_cast<float>(speed) / 100;
return speed < 0 ? -1 / (f_speed - 1) : 1 + f_speed;
}
string formatSpeed(int speed) {
stringstream ss;
ss
<< (speed >= 0 ? "x " : "/ ")
<< std::setw(4) << std::fixed << std::setprecision(2)
<< (1 + static_cast<float>(speed < 0 ? -speed : speed) / 100);
return ss.str();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
const GUI::Font& font, int max_w, int max_h)
@ -121,13 +162,13 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
wid.push_back(myPAspectRatio);
ypos += lineHeight + VGAP;
// Framerate
myFrameRate =
// Speed
mySpeed =
new SliderWidget(myTab, font, xpos, ypos-1, swidth, lineHeight,
"Frame rate ", lwidth, kFrameRateChanged, fontWidth * 6, "fps");
myFrameRate->setMinValue(0); myFrameRate->setMaxValue(900);
myFrameRate->setStepValue(10);
wid.push_back(myFrameRate);
"Speed ", lwidth, kSpeedupChanged, fontWidth * 8, "");
mySpeed->setMinValue(MIN_SPEED); mySpeed->setMaxValue(MAX_SPEED);
mySpeed->setStepValue(SPEED_STEP);
wid.push_back(mySpeed);
ypos += lineHeight + VGAP;
// Use sync to vblank
@ -141,7 +182,7 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
"(*) Requires application restart");
// Move over to the next column
xpos += myFrameRate->getWidth() + 16;
xpos += mySpeed->getWidth() + 16;
ypos = VBORDER;
// Fullscreen
@ -327,12 +368,10 @@ void VideoDialog::loadConfig()
myNAspectRatio->setValue(instance().settings().getInt("tia.aspectn"));
myPAspectRatio->setValue(instance().settings().getInt("tia.aspectp"));
// Framerate (0 or -1 means automatic framerate calculation)
int rate = instance().settings().getInt("framerate");
myFrameRate->setValue(rate < 0 ? 0 : rate);
myFrameRate->setValueLabel(rate <= 0 ? "Auto" :
instance().settings().getString("framerate"));
myFrameRate->setValueUnit(rate <= 0 ? "" : "fps");
// Emulation speed
int speed = mapSpeed(instance().settings().getFloat("speed"));
mySpeed->setValue(speed);
mySpeed->setValueLabel(formatSpeed(speed));
// Fullscreen
myFullscreen->setState(instance().settings().getBool("fullscreen"));
@ -404,15 +443,10 @@ void VideoDialog::saveConfig()
instance().settings().setValue("tia.aspectn", myNAspectRatio->getValueLabel());
instance().settings().setValue("tia.aspectp", myPAspectRatio->getValueLabel());
// Framerate
int f = myFrameRate->getValue();
instance().settings().setValue("framerate", f);
if(instance().hasConsole())
{
// Make sure auto-frame calculation is only enabled when necessary
instance().console().tia().enableAutoFrame(f <= 0);
instance().console().setFramerate(float(f));
}
// Speed
int speedup = mySpeed->getValue();
instance().settings().setValue("speed", unmapSpeed(speedup));
if (instance().hasConsole()) instance().console().initializeAudio();
// Fullscreen
instance().settings().setValue("fullscreen", myFullscreen->getState());
@ -486,7 +520,7 @@ void VideoDialog::setDefaults()
myTIAInterpolate->setState(false);
myNAspectRatio->setValue(91);
myPAspectRatio->setValue(109);
myFrameRate->setValue(0);
mySpeed->setValue(0);
myFullscreen->setState(false);
//myFullScreenMode->setSelectedIndex(0);
@ -585,10 +619,8 @@ void VideoDialog::handleCommand(CommandSender* sender, int cmd,
setDefaults();
break;
case kFrameRateChanged:
if(myFrameRate->getValue() == 0)
myFrameRate->setValueLabel("Auto");
myFrameRate->setValueUnit(myFrameRate->getValue() == 0 ? "" : "fps");
case kSpeedupChanged:
mySpeed->setValueLabel(formatSpeed(mySpeed->getValue()));
break;
case kTVModeChanged:

View File

@ -58,7 +58,7 @@ class VideoDialog : public Dialog
CheckboxWidget* myTIAInterpolate;
SliderWidget* myNAspectRatio;
SliderWidget* myPAspectRatio;
SliderWidget* myFrameRate;
SliderWidget* mySpeed;
CheckboxWidget* myFullscreen;
//PopUpWidget* myFullScreenMode;
@ -99,7 +99,7 @@ class VideoDialog : public Dialog
ButtonWidget* myCloneCustom;
enum {
kFrameRateChanged = 'VDfr',
kSpeedupChanged = 'VDSp',
kTVModeChanged = 'VDtv',

View File

@ -104,7 +104,6 @@
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20F9FF08C603EC00A73076 /* TogglePixelWidget.hxx */; };
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20FA0108C603EC00A73076 /* ToggleWidget.hxx */; };
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D6CC10408C811A600B8F642 /* TiaZoomWidget.hxx */; };
2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2DE7242E08CE910900C889A8 /* TIASnd.hxx */; };
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D2331900900B5EF00613B1F /* AudioWidget.hxx */; };
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF5F096E269100A518FE /* EventMappingWidget.hxx */; };
2D91746A09BA90380026E9FF /* InputDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF61096E269100A518FE /* InputDialog.hxx */; };
@ -202,7 +201,6 @@
2D91750309BA90380026E9FF /* TogglePixelWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F9FE08C603EC00A73076 /* TogglePixelWidget.cxx */; };
2D91750409BA90380026E9FF /* ToggleWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20FA0008C603EC00A73076 /* ToggleWidget.cxx */; };
2D91750609BA90380026E9FF /* TiaZoomWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D6CC10308C811A600B8F642 /* TiaZoomWidget.cxx */; };
2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2DE7242D08CE910900C889A8 /* TIASnd.cxx */; };
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D23318F0900B5EF00613B1F /* AudioWidget.cxx */; };
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF5E096E269100A518FE /* EventMappingWidget.cxx */; };
2D91750C09BA90380026E9FF /* InputDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF60096E269100A518FE /* InputDialog.cxx */; };
@ -511,6 +509,9 @@
DCC527D610B9DA19005E1287 /* System.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC527CE10B9DA19005E1287 /* System.cxx */; };
DCC527D710B9DA19005E1287 /* System.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527CF10B9DA19005E1287 /* System.hxx */; };
DCC527DB10B9DA6A005E1287 /* bspf.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527D810B9DA6A005E1287 /* bspf.hxx */; };
DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4AE20A2622500863C59 /* Resampler.hxx */; };
DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */; };
DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */; };
DCCA26B31FA64D5E000EE4D8 /* AbstractFrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */; };
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */; };
DCCF47DE14B60DEE00814FAB /* ControllerWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */; };
@ -564,6 +565,8 @@
DCDE17FB17724E5D00EB1AC6 /* ConfigPathDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */; };
DCDE17FC17724E5D00EB1AC6 /* SnapshotDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */; };
DCDE17FD17724E5D00EB1AC6 /* SnapshotDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */; };
DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */; };
DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDFF08020B781B0001227C0 /* DispatchResult.hxx */; };
DCE395DB16CB0B2B008DB1E5 /* FSNodePOSIX.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */; };
DCE395EF16CB0B5F008DB1E5 /* FSNodeFactory.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */; };
DCE395F016CB0B5F008DB1E5 /* FSNodeZIP.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */; };
@ -602,7 +605,6 @@
DCF3A6FD1DFC75E3008A8AF3 /* Playfield.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */; };
DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */; };
DCF3A6FF1DFC75E3008A8AF3 /* TIA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */; };
DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */; };
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467B40F93993B00B25D7A /* SoundNull.hxx */; };
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467BC0F9399F500B25D7A /* Version.hxx */; };
DCF467C20F939A1400B25D7A /* CartEF.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF467BE0F939A1400B25D7A /* CartEF.cxx */; };
@ -615,6 +617,8 @@
DCF7B0DF10A762FC007A2870 /* CartFA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF7B0DB10A762FC007A2870 /* CartFA.cxx */; };
DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF7B0DC10A762FC007A2870 /* CartFA.hxx */; };
DCFB9FAC1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */; };
DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */; };
DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */; };
DCFF14CD18B0260300A20364 /* EventHandlerSDL2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */; };
DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; };
DCFFE59D12100E1400DFA000 /* ComboDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */; };
@ -625,8 +629,20 @@
E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E091F93E915003DDD52 /* JitterEmulation.cxx */; };
E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; };
E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; };
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */; };
E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */; };
E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; };
E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */; };
E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4139201E901C004A3391 /* AudioQueue.hxx */; };
E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413A201E901D004A3391 /* AudioQueue.cxx */; };
E09F4141201E9050004A3391 /* Audio.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F413D201E904F004A3391 /* Audio.hxx */; };
E09F4142201E9050004A3391 /* Audio.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413E201E904F004A3391 /* Audio.cxx */; };
E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413F201E904F004A3391 /* AudioChannel.cxx */; };
E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4140201E904F004A3391 /* AudioChannel.hxx */; };
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */; };
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */; };
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */; };
E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */; };
/* End PBXBuildFile section */
/* Begin PBXBuildRule section */
@ -874,8 +890,6 @@
2DE2DF8D0627AE34006BEC99 /* Sound.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Sound.hxx; sourceTree = "<group>"; };
2DE2DF8E0627AE34006BEC99 /* Switches.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Switches.cxx; sourceTree = "<group>"; };
2DE2DF8F0627AE34006BEC99 /* Switches.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Switches.hxx; sourceTree = "<group>"; };
2DE7242D08CE910900C889A8 /* TIASnd.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TIASnd.cxx; sourceTree = "<group>"; };
2DE7242E08CE910900C889A8 /* TIASnd.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = TIASnd.hxx; sourceTree = "<group>"; };
2DEB3D4C0629BD24007EBBD3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
2DEF21F808BC033500B246B4 /* CheckListWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CheckListWidget.cxx; sourceTree = "<group>"; };
2DEF21F908BC033500B246B4 /* CheckListWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CheckListWidget.hxx; sourceTree = "<group>"; };
@ -1187,6 +1201,9 @@
DCC527CE10B9DA19005E1287 /* System.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = System.cxx; sourceTree = "<group>"; };
DCC527CF10B9DA19005E1287 /* System.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = System.hxx; sourceTree = "<group>"; };
DCC527D810B9DA6A005E1287 /* bspf.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = bspf.hxx; sourceTree = "<group>"; };
DCC6A4AE20A2622500863C59 /* Resampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Resampler.hxx; path = audio/Resampler.hxx; sourceTree = "<group>"; };
DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SimpleResampler.cxx; path = audio/SimpleResampler.cxx; sourceTree = "<group>"; };
DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SimpleResampler.hxx; path = audio/SimpleResampler.hxx; sourceTree = "<group>"; };
DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AbstractFrameManager.hxx; sourceTree = "<group>"; };
DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = "<group>"; };
DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ControllerWidget.hxx; sourceTree = "<group>"; };
@ -1240,6 +1257,8 @@
DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfigPathDialog.hxx; sourceTree = "<group>"; };
DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SnapshotDialog.cxx; sourceTree = "<group>"; };
DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SnapshotDialog.hxx; sourceTree = "<group>"; };
DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DispatchResult.cxx; sourceTree = "<group>"; };
DCDFF08020B781B0001227C0 /* DispatchResult.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DispatchResult.hxx; sourceTree = "<group>"; };
DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodePOSIX.hxx; sourceTree = "<group>"; };
DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodeFactory.hxx; sourceTree = "<group>"; };
DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FSNodeZIP.cxx; sourceTree = "<group>"; };
@ -1278,7 +1297,6 @@
DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Playfield.hxx; sourceTree = "<group>"; };
DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cxx; sourceTree = "<group>"; };
DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hxx; sourceTree = "<group>"; };
DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIATypes.hxx; sourceTree = "<group>"; };
DCF467B40F93993B00B25D7A /* SoundNull.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundNull.hxx; sourceTree = "<group>"; };
DCF467BC0F9399F500B25D7A /* Version.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Version.hxx; sourceTree = "<group>"; };
DCF467BE0F939A1400B25D7A /* CartEF.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartEF.cxx; sourceTree = "<group>"; };
@ -1291,6 +1309,8 @@
DCF7B0DB10A762FC007A2870 /* CartFA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartFA.cxx; sourceTree = "<group>"; };
DCF7B0DC10A762FC007A2870 /* CartFA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartFA.hxx; sourceTree = "<group>"; };
DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueIteratorImpl.hxx; sourceTree = "<group>"; };
DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationWorker.cxx; sourceTree = "<group>"; };
DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationWorker.hxx; sourceTree = "<group>"; };
DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventHandlerSDL2.cxx; sourceTree = "<group>"; };
DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EventHandlerSDL2.hxx; sourceTree = "<group>"; };
DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComboDialog.cxx; sourceTree = "<group>"; };
@ -1301,6 +1321,18 @@
E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = "<group>"; };
E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = "<group>"; };
E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = "<group>"; };
E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationTiming.cxx; sourceTree = "<group>"; };
E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationTiming.hxx; sourceTree = "<group>"; };
E09F4139201E901C004A3391 /* AudioQueue.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioQueue.hxx; sourceTree = "<group>"; };
E09F413A201E901D004A3391 /* AudioQueue.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioQueue.cxx; sourceTree = "<group>"; };
E09F413D201E904F004A3391 /* Audio.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Audio.hxx; sourceTree = "<group>"; };
E09F413E201E904F004A3391 /* Audio.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cxx; sourceTree = "<group>"; };
E09F413F201E904F004A3391 /* AudioChannel.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioChannel.cxx; sourceTree = "<group>"; };
E09F4140201E904F004A3391 /* AudioChannel.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioChannel.hxx; sourceTree = "<group>"; };
E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = LanczosResampler.hxx; path = audio/LanczosResampler.hxx; sourceTree = "<group>"; };
E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LanczosResampler.cxx; path = audio/LanczosResampler.cxx; sourceTree = "<group>"; };
E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ConvolutionBuffer.hxx; path = audio/ConvolutionBuffer.hxx; sourceTree = "<group>"; };
E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ConvolutionBuffer.cxx; path = audio/ConvolutionBuffer.cxx; sourceTree = "<group>"; };
E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = "<group>"; };
E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = "<group>"; };
F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; };
@ -1578,6 +1610,9 @@
2D6050C5089876F300C6DE89 /* common */ = {
isa = PBXGroup;
children = (
DCC6A4AD20A2620D00863C59 /* audio */,
E09F413A201E901D004A3391 /* AudioQueue.cxx */,
E09F4139201E901C004A3391 /* AudioQueue.hxx */,
DC79F81017A88D9E00288B91 /* Base.cxx */,
DC79F81117A88D9E00288B91 /* Base.hxx */,
DCC527D810B9DA6A005E1287 /* bspf.hxx */,
@ -1756,8 +1791,14 @@
2DE2DF3B0627AE07006BEC99 /* Control.hxx */,
DC932D3F0F278A5200FEFEFC /* DefProps.hxx */,
DCC527C910B9DA19005E1287 /* Device.hxx */,
DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */,
DCDFF08020B781B0001227C0 /* DispatchResult.hxx */,
2DE2DF3E0627AE07006BEC99 /* Driving.cxx */,
2DE2DF3F0627AE07006BEC99 /* Driving.hxx */,
E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */,
E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */,
DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */,
DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */,
2DE2DF410627AE07006BEC99 /* Event.hxx */,
2D733D6E062895B2006265D9 /* EventHandler.cxx */,
2D733D6F062895B2006265D9 /* EventHandler.hxx */,
@ -1815,11 +1856,8 @@
DCD2839612E39F1200A808DC /* Thumbulator.cxx */,
DCD2839712E39F1200A808DC /* Thumbulator.hxx */,
DCE903E31DF5DCD10080A7F3 /* tia */,
2DE7242D08CE910900C889A8 /* TIASnd.cxx */,
2DE7242E08CE910900C889A8 /* TIASnd.hxx */,
DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */,
DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */,
DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */,
DC1B2EC21E50036100F62837 /* TrakBall.hxx */,
);
path = emucore;
@ -2020,6 +2058,20 @@
path = tv_filters;
sourceTree = "<group>";
};
DCC6A4AD20A2620D00863C59 /* audio */ = {
isa = PBXGroup;
children = (
E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */,
E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */,
E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */,
E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */,
DCC6A4AE20A2622500863C59 /* Resampler.hxx */,
DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */,
DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */,
);
name = audio;
sourceTree = "<group>";
};
DCCC0C9109C3541E0088BFF1 /* cheat */ = {
isa = PBXGroup;
children = (
@ -2070,6 +2122,10 @@
DCE903E31DF5DCD10080A7F3 /* tia */ = {
isa = PBXGroup;
children = (
E09F413E201E904F004A3391 /* Audio.cxx */,
E09F413D201E904F004A3391 /* Audio.hxx */,
E09F413F201E904F004A3391 /* AudioChannel.cxx */,
E09F4140201E904F004A3391 /* AudioChannel.hxx */,
DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */,
DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */,
DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */,
@ -2124,11 +2180,13 @@
buildActionMask = 2147483647;
files = (
2D9173CB09BA90380026E9FF /* SDLMain.h in Headers */,
E09F4141201E9050004A3391 /* Audio.hxx in Headers */,
2D9173CC09BA90380026E9FF /* Booster.hxx in Headers */,
2D9173CD09BA90380026E9FF /* Cart.hxx in Headers */,
2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */,
2D9173CF09BA90380026E9FF /* Cart3F.hxx in Headers */,
DC3EE86D1E2C0E6D00905161 /* zlib.h in Headers */,
E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */,
DC3EE86A1E2C0E6D00905161 /* trees.h in Headers */,
2D9173D009BA90380026E9FF /* Cart4K.hxx in Headers */,
2D9173D109BA90380026E9FF /* CartAR.hxx in Headers */,
@ -2171,6 +2229,7 @@
2D9173EE09BA90380026E9FF /* Serializer.hxx in Headers */,
2D9173EF09BA90380026E9FF /* Sound.hxx in Headers */,
2D9173F009BA90380026E9FF /* Switches.hxx in Headers */,
E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */,
2D9173F909BA90380026E9FF /* EventHandler.hxx in Headers */,
2D9173FA09BA90380026E9FF /* FrameBuffer.hxx in Headers */,
2D9173FB09BA90380026E9FF /* Settings.hxx in Headers */,
@ -2186,6 +2245,7 @@
2D91740309BA90380026E9FF /* Command.hxx in Headers */,
DC3EE85B1E2C0E6D00905161 /* deflate.h in Headers */,
2D91740409BA90380026E9FF /* Dialog.hxx in Headers */,
E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */,
2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */,
DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */,
2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */,
@ -2223,6 +2283,7 @@
DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */,
2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */,
DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */,
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */,
2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */,
2D91742909BA90380026E9FF /* TIADebug.hxx in Headers */,
2D91742A09BA90380026E9FF /* YaccParser.hxx in Headers */,
@ -2246,6 +2307,7 @@
2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */,
2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */,
2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */,
DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */,
DCF3A6EC1DFC75E3008A8AF3 /* DelayQueue.hxx in Headers */,
DCA078341F8C1B04008EFEE5 /* LinkedObjectPool.hxx in Headers */,
2D91745809BA90380026E9FF /* DebuggerDialog.hxx in Headers */,
@ -2267,7 +2329,6 @@
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */,
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */,
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */,
2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */,
DC1BC6672066B4390076F74A /* PKeyboardHandler.hxx in Headers */,
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */,
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */,
@ -2285,6 +2346,7 @@
DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */,
DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */,
DCE3BBFA0C95CEDC00A671DF /* RomInfoWidget.hxx in Headers */,
DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */,
DC6DC91F205DB879004A5FC3 /* PhysicalJoystick.hxx in Headers */,
DC0984860D3985160073C852 /* CartSB.hxx in Headers */,
DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */,
@ -2298,6 +2360,7 @@
DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */,
DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */,
DC0DF86A0F0DAAF500B0F1F3 /* GlobalPropsDialog.hxx in Headers */,
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */,
DC5D2C520F117CFD004D1660 /* Rect.hxx in Headers */,
DC71EAA81FDA070D008827CB /* CartMNetworkWidget.hxx in Headers */,
DC5D2C530F117CFD004D1660 /* StellaFont.hxx in Headers */,
@ -2309,7 +2372,6 @@
DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */,
DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */,
DC9EA8880F729A36000452B5 /* KidVid.hxx in Headers */,
DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */,
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */,
DCBDDE9F1D6A5F2F009DF1E9 /* Cart3EPlus.hxx in Headers */,
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */,
@ -2350,6 +2412,7 @@
DC74D6A2138D4D7E00F05C5C /* StringParser.hxx in Headers */,
DC2874071F8F2278004BF21A /* TrapArray.hxx in Headers */,
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */,
DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */,
DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */,
DC8C1BAE14B25DE7006440EE /* CartCM.hxx in Headers */,
DCF3A6FB1DFC75E3008A8AF3 /* Player.hxx in Headers */,
@ -2386,6 +2449,7 @@
DC6DC921205DB879004A5FC3 /* PJoystickHandler.hxx in Headers */,
DCAAE5DF1715887B0080BB82 /* CartEFSCWidget.hxx in Headers */,
DCAAE5E11715887B0080BB82 /* CartEFWidget.hxx in Headers */,
DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */,
DCAAE5E31715887B0080BB82 /* CartF0Widget.hxx in Headers */,
DCAAE5E51715887B0080BB82 /* CartF4SCWidget.hxx in Headers */,
DCAAE5E71715887B0080BB82 /* CartF4Widget.hxx in Headers */,
@ -2551,6 +2615,7 @@
DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */,
2D91747609BA90380026E9FF /* Cart.cxx in Sources */,
2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */,
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */,
2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */,
2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */,
2D91747A09BA90380026E9FF /* CartAR.cxx in Sources */,
@ -2559,9 +2624,11 @@
DCF3A6FC1DFC75E3008A8AF3 /* Playfield.cxx in Sources */,
2D91747C09BA90380026E9FF /* CartDPC.cxx in Sources */,
2D91747D09BA90380026E9FF /* CartE0.cxx in Sources */,
E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */,
2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */,
DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */,
2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */,
DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */,
2D91748009BA90380026E9FF /* CartF4SC.cxx in Sources */,
2D91748109BA90380026E9FF /* CartF6.cxx in Sources */,
2D91748209BA90380026E9FF /* CartF6SC.cxx in Sources */,
@ -2578,6 +2645,7 @@
2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */,
2D91749009BA90380026E9FF /* M6532.cxx in Sources */,
2D91749109BA90380026E9FF /* MD5.cxx in Sources */,
E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */,
DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */,
2D91749309BA90380026E9FF /* Paddles.cxx in Sources */,
2D91749409BA90380026E9FF /* Props.cxx in Sources */,
@ -2650,6 +2718,7 @@
2D9174FA09BA90380026E9FF /* DebuggerDialog.cxx in Sources */,
DCF3A6E91DFC75E3008A8AF3 /* Ball.cxx in Sources */,
2D9174FB09BA90380026E9FF /* PromptWidget.cxx in Sources */,
DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */,
2D9174FC09BA90380026E9FF /* RamWidget.cxx in Sources */,
DC2AADAE194F389C0026C7A4 /* CartDASH.cxx in Sources */,
2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */,
@ -2668,7 +2737,7 @@
DC71EA9D1FDA06D2008827CB /* CartE78K.cxx in Sources */,
DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */,
DCDDEAC41F5DBF0400C67366 /* RewindManager.cxx in Sources */,
2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */,
E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */,
DC71EA9F1FDA06D2008827CB /* CartMNetwork.cxx in Sources */,
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */,
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */,
@ -2753,6 +2822,7 @@
DC8C1BAD14B25DE7006440EE /* CartCM.cxx in Sources */,
DCDDEAC61F5DBF0400C67366 /* StateManager.cxx in Sources */,
DC8C1BAF14B25DE7006440EE /* CompuMate.cxx in Sources */,
E09F4142201E9050004A3391 /* Audio.cxx in Sources */,
DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */,
DCCF47DF14B60DEE00814FAB /* JoystickWidget.cxx in Sources */,
DCCF49B714B7544A00814FAB /* PaddleWidget.cxx in Sources */,
@ -2774,6 +2844,7 @@
DC3EE8601E2C0E6D00905161 /* gzwrite.c in Sources */,
DCAAE5D71715887B0080BB82 /* Cart4KWidget.cxx in Sources */,
DCDA03B01A2009BB00711920 /* CartWD.cxx in Sources */,
DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */,
DCAAE5D91715887B0080BB82 /* Cart0840Widget.cxx in Sources */,
DCAAE5DB1715887B0080BB82 /* CartCVWidget.cxx in Sources */,
DCAAE5DE1715887B0080BB82 /* CartEFSCWidget.cxx in Sources */,
@ -2802,6 +2873,7 @@
DC676A4D1729A0B000E4E73D /* CartDPCWidget.cxx in Sources */,
DC676A4F1729A0B000E4E73D /* CartE0Widget.cxx in Sources */,
DC676A511729A0B000E4E73D /* CartE7Widget.cxx in Sources */,
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */,
DC676A531729A0B000E4E73D /* CartFA2Widget.cxx in Sources */,
DC676A551729A0B000E4E73D /* CartFEWidget.cxx in Sources */,
DC676A591729A0B000E4E73D /* CartSBWidget.cxx in Sources */,

View File

@ -20,6 +20,4 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SettingsWINDOWS::SettingsWINDOWS(OSystem& osystem)
: Settings(osystem)
{
setInternal("fragsize", "1024");
}
{}

View File

@ -228,6 +228,10 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\common\AudioQueue.cxx" />
<ClCompile Include="..\common\audio\ConvolutionBuffer.cxx" />
<ClCompile Include="..\common\audio\LanczosResampler.cxx" />
<ClCompile Include="..\common\audio\SimpleResampler.cxx" />
<ClCompile Include="..\common\Base.cxx" />
<ClCompile Include="..\common\EventHandlerSDL2.cxx" />
<ClCompile Include="..\common\FBSurfaceSDL2.cxx" />
@ -323,10 +327,15 @@
<ClCompile Include="..\emucore\CartE78K.cxx" />
<ClCompile Include="..\emucore\CartWD.cxx" />
<ClCompile Include="..\emucore\CompuMate.cxx" />
<ClCompile Include="..\emucore\DispatchResult.cxx" />
<ClCompile Include="..\emucore\EmulationTiming.cxx" />
<ClCompile Include="..\emucore\EmulationWorker.cxx" />
<ClCompile Include="..\emucore\FBSurface.cxx" />
<ClCompile Include="..\emucore\MindLink.cxx" />
<ClCompile Include="..\emucore\PointingDevice.cxx" />
<ClCompile Include="..\emucore\TIASurface.cxx" />
<ClCompile Include="..\emucore\tia\Audio.cxx" />
<ClCompile Include="..\emucore\tia\AudioChannel.cxx" />
<ClCompile Include="..\emucore\tia\Background.cxx" />
<ClCompile Include="..\emucore\tia\Ball.cxx" />
<ClCompile Include="..\emucore\tia\DrawCounterDecodes.cxx" />
@ -410,7 +419,6 @@
<ClCompile Include="..\emucore\Switches.cxx" />
<ClCompile Include="..\emucore\System.cxx" />
<ClCompile Include="..\emucore\Thumbulator.cxx" />
<ClCompile Include="..\emucore\TIASnd.cxx" />
<ClCompile Include="..\cheat\BankRomCheat.cxx" />
<ClCompile Include="..\cheat\CheatCodeDialog.cxx" />
<ClCompile Include="..\cheat\CheatManager.cxx" />
@ -511,6 +519,11 @@
<ClCompile Include="..\libpng\pngwutil.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\common\AudioQueue.hxx" />
<ClInclude Include="..\common\audio\ConvolutionBuffer.hxx" />
<ClInclude Include="..\common\audio\LanczosResampler.hxx" />
<ClInclude Include="..\common\audio\Resampler.hxx" />
<ClInclude Include="..\common\audio\SimpleResampler.hxx" />
<ClInclude Include="..\common\Base.hxx" />
<ClInclude Include="..\common\bspf.hxx" />
<ClInclude Include="..\common\EventHandlerSDL2.hxx" />
@ -620,12 +633,17 @@
<ClInclude Include="..\emucore\CartE78K.hxx" />
<ClInclude Include="..\emucore\CartWD.hxx" />
<ClInclude Include="..\emucore\CompuMate.hxx" />
<ClInclude Include="..\emucore\DispatchResult.hxx" />
<ClInclude Include="..\emucore\EmulationTiming.hxx" />
<ClInclude Include="..\emucore\EmulationWorker.hxx" />
<ClInclude Include="..\emucore\EventHandlerConstants.hxx" />
<ClInclude Include="..\emucore\FBSurface.hxx" />
<ClInclude Include="..\emucore\FrameBufferConstants.hxx" />
<ClInclude Include="..\emucore\MindLink.hxx" />
<ClInclude Include="..\emucore\PointingDevice.hxx" />
<ClInclude Include="..\emucore\TIASurface.hxx" />
<ClInclude Include="..\emucore\tia\Audio.hxx" />
<ClInclude Include="..\emucore\tia\AudioChannel.hxx" />
<ClInclude Include="..\emucore\tia\Background.hxx" />
<ClInclude Include="..\emucore\tia\Ball.hxx" />
<ClInclude Include="..\emucore\tia\DelayQueue.hxx" />
@ -733,7 +751,6 @@
<ClInclude Include="..\emucore\Switches.hxx" />
<ClInclude Include="..\emucore\System.hxx" />
<ClInclude Include="..\emucore\Thumbulator.hxx" />
<ClInclude Include="..\emucore\TIASnd.hxx" />
<ClInclude Include="..\debugger\gui\AudioWidget.hxx" />
<ClInclude Include="..\debugger\CartDebug.hxx" />
<ClInclude Include="..\debugger\CpuDebug.hxx" />

View File

@ -55,6 +55,12 @@
<Filter Include="Source Files\emucore\tia">
<UniqueIdentifier>{ffa3642d-aa8a-43a5-8ac5-acd8878dd091}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\audio">
<UniqueIdentifier>{49d8ea64-20c1-45f1-9dc9-b39c17d7cabd}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\audio">
<UniqueIdentifier>{000e4a6b-8cd6-43db-8253-8255c7efa706}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\common\FrameBufferSDL2.cxx">
@ -237,9 +243,6 @@
<ClCompile Include="..\emucore\Thumbulator.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
<ClCompile Include="..\emucore\TIASnd.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
<ClCompile Include="..\cheat\BankRomCheat.cxx">
<Filter>Source Files\cheat</Filter>
</ClCompile>
@ -900,6 +903,33 @@
<ClCompile Include="..\common\PKeyboardHandler.cxx">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\emucore\tia\Audio.cxx">
<Filter>Source Files\emucore\tia</Filter>
</ClCompile>
<ClCompile Include="..\emucore\tia\AudioChannel.cxx">
<Filter>Source Files\emucore\tia</Filter>
</ClCompile>
<ClCompile Include="..\common\AudioQueue.cxx">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\emucore\EmulationTiming.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
<ClCompile Include="..\common\audio\SimpleResampler.cxx">
<Filter>Source Files\audio</Filter>
</ClCompile>
<ClCompile Include="..\common\audio\ConvolutionBuffer.cxx">
<Filter>Source Files\audio</Filter>
</ClCompile>
<ClCompile Include="..\common\audio\LanczosResampler.cxx">
<Filter>Source Files\audio</Filter>
</ClCompile>
<ClCompile Include="..\emucore\DispatchResult.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
<ClCompile Include="..\emucore\EmulationWorker.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\common\bspf.hxx">
@ -1112,9 +1142,6 @@
<ClInclude Include="..\emucore\Thumbulator.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
<ClInclude Include="..\emucore\TIASnd.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
<ClInclude Include="..\debugger\gui\AudioWidget.hxx">
<Filter>Header Files\debugger</Filter>
</ClInclude>
@ -1844,6 +1871,36 @@
<ClInclude Include="..\common\PKeyboardHandler.hxx">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\emucore\tia\Audio.hxx">
<Filter>Header Files\emucore\tia</Filter>
</ClInclude>
<ClInclude Include="..\emucore\tia\AudioChannel.hxx">
<Filter>Header Files\emucore\tia</Filter>
</ClInclude>
<ClInclude Include="..\common\AudioQueue.hxx">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\emucore\EmulationTiming.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
<ClInclude Include="..\common\audio\Resampler.hxx">
<Filter>Header Files\audio</Filter>
</ClInclude>
<ClInclude Include="..\common\audio\SimpleResampler.hxx">
<Filter>Header Files\audio</Filter>
</ClInclude>
<ClInclude Include="..\common\audio\ConvolutionBuffer.hxx">
<Filter>Header Files\audio</Filter>
</ClInclude>
<ClInclude Include="..\common\audio\LanczosResampler.hxx">
<Filter>Header Files\audio</Filter>
</ClInclude>
<ClInclude Include="..\emucore\DispatchResult.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
<ClInclude Include="..\emucore\EmulationWorker.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="stella.ico">