mirror of https://github.com/stella-emu/stella.git
Merge branch 'feature/precise-audio'
This commit is contained in:
commit
c270a45409
|
@ -26,6 +26,39 @@
|
||||||
"string_view": "cpp",
|
"string_view": "cpp",
|
||||||
"system_error": "cpp",
|
"system_error": "cpp",
|
||||||
"vector": "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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -101,7 +101,8 @@ MODULES += \
|
||||||
src/emucore/tia/frame-manager \
|
src/emucore/tia/frame-manager \
|
||||||
src/gui \
|
src/gui \
|
||||||
src/common \
|
src/common \
|
||||||
src/common/tv_filters
|
src/common/tv_filters \
|
||||||
|
src/common/audio
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# The build rules follow - normally you should have no need to
|
# The build rules follow - normally you should have no need to
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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)));
|
||||||
|
}
|
|
@ -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
|
|
@ -144,10 +144,6 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
|
||||||
// Always recreate renderer (some systems need this)
|
// Always recreate renderer (some systems need this)
|
||||||
if(myRenderer)
|
if(myRenderer)
|
||||||
{
|
{
|
||||||
// Always clear the (double-buffered) renderer surface
|
|
||||||
SDL_RenderClear(myRenderer);
|
|
||||||
SDL_RenderPresent(myRenderer);
|
|
||||||
SDL_RenderClear(myRenderer);
|
|
||||||
SDL_DestroyRenderer(myRenderer);
|
SDL_DestroyRenderer(myRenderer);
|
||||||
myRenderer = nullptr;
|
myRenderer = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +230,12 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
|
||||||
myOSystem.logMessage(msg, 0);
|
myOSystem.logMessage(msg, 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always clear the (double-buffered) renderer surface
|
||||||
|
SDL_RenderClear(myRenderer);
|
||||||
|
SDL_RenderPresent(myRenderer);
|
||||||
|
SDL_RenderClear(myRenderer);
|
||||||
|
|
||||||
SDL_RendererInfo renderinfo;
|
SDL_RendererInfo renderinfo;
|
||||||
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
|
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
|
||||||
myOSystem.settings().setValue("video", renderinfo.name);
|
myOSystem.settings().setValue("video", renderinfo.name);
|
||||||
|
@ -241,6 +243,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void FrameBufferSDL2::setTitle(const string& title)
|
void FrameBufferSDL2::setTitle(const string& title)
|
||||||
{
|
{
|
||||||
myScreenTitle = title;
|
myScreenTitle = title;
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
#include "SoundNull.hxx"
|
#include "SoundNull.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class AudioSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class deals with the different framebuffer/sound/event
|
This class deals with the different framebuffer/sound/event
|
||||||
implementations for the various ports of Stella, and always returns a
|
implementations for the various ports of Stella, and always returns a
|
||||||
|
@ -108,10 +110,10 @@ class MediaFactory
|
||||||
return make_unique<FrameBufferSDL2>(osystem);
|
return make_unique<FrameBufferSDL2>(osystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
static unique_ptr<Sound> createAudio(OSystem& osystem)
|
static unique_ptr<Sound> createAudio(OSystem& osystem, AudioSettings& audioSettings)
|
||||||
{
|
{
|
||||||
#ifdef SOUND_SUPPORT
|
#ifdef SOUND_SUPPORT
|
||||||
return make_unique<SoundSDL2>(osystem);
|
return make_unique<SoundSDL2>(osystem, audioSettings);
|
||||||
#else
|
#else
|
||||||
return make_unique<SoundNull>(osystem);
|
return make_unique<SoundNull>(osystem);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "Sound.hxx"
|
#include "Sound.hxx"
|
||||||
#include "OSystem.hxx"
|
#include "OSystem.hxx"
|
||||||
|
#include "AudioQueue.hxx"
|
||||||
|
#include "EmulationTiming.hxx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class implements a Null sound object, where-by sound generation
|
This class implements a Null sound object, where-by sound generation
|
||||||
|
@ -53,26 +55,11 @@ class SoundNull : public Sound
|
||||||
*/
|
*/
|
||||||
void setEnabled(bool state) override { }
|
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
|
Initializes the sound device. This must be called before any
|
||||||
calls are made to derived methods.
|
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
|
Should be called to close the sound device. Once called the sound
|
||||||
|
@ -92,15 +79,6 @@ class SoundNull : public Sound
|
||||||
*/
|
*/
|
||||||
void reset() override { }
|
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
|
Sets the volume of the sound device to the specified level. The
|
||||||
volume is given as a percentage from 0 to 100. Values outside
|
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
|
@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.
|
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 { }
|
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:
|
private:
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
SoundNull() = delete;
|
SoundNull() = delete;
|
||||||
|
|
|
@ -22,46 +22,38 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "SDL_lib.hxx"
|
#include "SDL_lib.hxx"
|
||||||
#include "TIASnd.hxx"
|
|
||||||
#include "TIAConstants.hxx"
|
|
||||||
#include "FrameBuffer.hxx"
|
#include "FrameBuffer.hxx"
|
||||||
#include "Settings.hxx"
|
#include "Settings.hxx"
|
||||||
#include "System.hxx"
|
#include "System.hxx"
|
||||||
#include "OSystem.hxx"
|
#include "OSystem.hxx"
|
||||||
#include "Console.hxx"
|
#include "Console.hxx"
|
||||||
#include "SoundSDL2.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),
|
: Sound(osystem),
|
||||||
myIsEnabled(false),
|
|
||||||
myIsInitializedFlag(false),
|
myIsInitializedFlag(false),
|
||||||
myLastRegisterSetCycle(0),
|
myVolume(100),
|
||||||
myNumChannels(0),
|
myVolumeFactor(0xffff),
|
||||||
myFragmentSizeLogBase2(0),
|
myCurrentFragment(nullptr),
|
||||||
myFragmentSizeLogDiv1(0),
|
myAudioSettings(audioSettings)
|
||||||
myFragmentSizeLogDiv2(0),
|
|
||||||
myIsMuted(true),
|
|
||||||
myVolume(100)
|
|
||||||
{
|
{
|
||||||
myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2);
|
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
|
// The sound system is opened only once per program run, to eliminate
|
||||||
// issues with opening and closing it multiple times
|
// issues with opening and closing it multiple times
|
||||||
// This fixes a bug most prevalent with ATI video cards in Windows,
|
// This fixes a bug most prevalent with ATI video cards in Windows,
|
||||||
// whereby sound stopped working after the first video change
|
// whereby sound stopped working after the first video change
|
||||||
SDL_AudioSpec desired;
|
SDL_AudioSpec desired;
|
||||||
desired.freq = myOSystem.settings().getInt("freq");
|
desired.freq = myAudioSettings.sampleRate();
|
||||||
desired.format = AUDIO_S16SYS;
|
desired.format = AUDIO_F32SYS;
|
||||||
desired.channels = 2;
|
desired.channels = 2;
|
||||||
desired.samples = myOSystem.settings().getInt("fragsize");
|
desired.samples = myAudioSettings.fragmentSize();
|
||||||
desired.callback = callback;
|
desired.callback = callback;
|
||||||
desired.userdata = static_cast<void*>(this);
|
desired.userdata = static_cast<void*>(this);
|
||||||
|
|
||||||
|
@ -87,13 +79,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
||||||
return;
|
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;
|
myIsInitializedFlag = true;
|
||||||
SDL_PauseAudio(1);
|
|
||||||
|
mute(true);
|
||||||
|
|
||||||
myOSystem.logMessage("SoundSDL2::SoundSDL2 initialized", 2);
|
myOSystem.logMessage("SoundSDL2::SoundSDL2 initialized", 2);
|
||||||
}
|
}
|
||||||
|
@ -101,43 +89,43 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
SoundSDL2::~SoundSDL2()
|
SoundSDL2::~SoundSDL2()
|
||||||
{
|
{
|
||||||
// Close the SDL audio system if it's initialized
|
if (!myIsInitializedFlag) return;
|
||||||
if(myIsInitializedFlag)
|
|
||||||
{
|
SDL_CloseAudio();
|
||||||
SDL_CloseAudio();
|
|
||||||
myIsEnabled = myIsInitializedFlag = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void SoundSDL2::setEnabled(bool state)
|
void SoundSDL2::setEnabled(bool state)
|
||||||
{
|
{
|
||||||
myOSystem.settings().setValue("sound", state);
|
myAudioSettings.setEnabled(state);
|
||||||
|
if (myAudioQueue) myAudioQueue->ignoreOverflows(!state);
|
||||||
|
|
||||||
myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" :
|
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);
|
myOSystem.logMessage("SoundSDL2::open started ...", 2);
|
||||||
myIsEnabled = false;
|
|
||||||
mute(true);
|
mute(true);
|
||||||
if(!myIsInitializedFlag || !myOSystem.settings().getBool("sound"))
|
|
||||||
|
audioQueue->ignoreOverflows(!myAudioSettings.enabled());
|
||||||
|
if(!myAudioSettings.enabled())
|
||||||
{
|
{
|
||||||
myOSystem.logMessage("Sound disabled\n", 1);
|
myOSystem.logMessage("Sound disabled\n", 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now initialize the TIASound object which will actually generate sound
|
myAudioQueue = audioQueue;
|
||||||
myTIASound.outputFrequency(myHardwareSpec.freq);
|
myUnderrun = true;
|
||||||
const string& chanResult =
|
myCurrentFragment = nullptr;
|
||||||
myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2);
|
|
||||||
|
|
||||||
// Adjust volume to that defined in settings
|
// Adjust volume to that defined in settings
|
||||||
myVolume = myOSystem.settings().getInt("volume");
|
setVolume(myAudioSettings.volume());
|
||||||
setVolume(myVolume);
|
|
||||||
|
|
||||||
// Show some info
|
// Show some info
|
||||||
ostringstream buf;
|
ostringstream buf;
|
||||||
|
@ -146,12 +134,12 @@ void SoundSDL2::open()
|
||||||
<< " Frag size: " << uInt32(myHardwareSpec.samples) << endl
|
<< " Frag size: " << uInt32(myHardwareSpec.samples) << endl
|
||||||
<< " Frequency: " << uInt32(myHardwareSpec.freq) << endl
|
<< " Frequency: " << uInt32(myHardwareSpec.freq) << endl
|
||||||
<< " Channels: " << uInt32(myHardwareSpec.channels)
|
<< " Channels: " << uInt32(myHardwareSpec.channels)
|
||||||
<< " (" << chanResult << ")" << endl
|
|
||||||
<< endl;
|
<< endl;
|
||||||
myOSystem.logMessage(buf.str(), 1);
|
myOSystem.logMessage(buf.str(), 1);
|
||||||
|
|
||||||
|
initResampler();
|
||||||
|
|
||||||
// And start the SDL sound subsystem ...
|
// And start the SDL sound subsystem ...
|
||||||
myIsEnabled = true;
|
|
||||||
mute(false);
|
mute(false);
|
||||||
|
|
||||||
myOSystem.logMessage("SoundSDL2::open finished", 2);
|
myOSystem.logMessage("SoundSDL2::open finished", 2);
|
||||||
|
@ -160,15 +148,16 @@ void SoundSDL2::open()
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void SoundSDL2::close()
|
void SoundSDL2::close()
|
||||||
{
|
{
|
||||||
if(myIsInitializedFlag)
|
if(!myIsInitializedFlag) return;
|
||||||
{
|
|
||||||
myIsEnabled = false;
|
mute(true);
|
||||||
SDL_PauseAudio(1);
|
|
||||||
myLastRegisterSetCycle = 0;
|
if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment);
|
||||||
myTIASound.reset();
|
myAudioQueue.reset();
|
||||||
myRegWriteQueue.clear();
|
myCurrentFragment = nullptr;
|
||||||
myOSystem.logMessage("SoundSDL2::close", 2);
|
|
||||||
}
|
myOSystem.logMessage("SoundSDL2::close", 2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -176,33 +165,25 @@ void SoundSDL2::mute(bool state)
|
||||||
{
|
{
|
||||||
if(myIsInitializedFlag)
|
if(myIsInitializedFlag)
|
||||||
{
|
{
|
||||||
myIsMuted = state;
|
SDL_PauseAudio(state ? 1 : 0);
|
||||||
SDL_PauseAudio(myIsMuted ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void SoundSDL2::reset()
|
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);
|
myAudioSettings.setVolume(percent);
|
||||||
SDL_LockAudio();
|
|
||||||
myVolume = percent;
|
myVolume = percent;
|
||||||
myTIASound.volume(percent);
|
|
||||||
|
SDL_LockAudio();
|
||||||
|
myVolumeFactor = std::pow(static_cast<float>(percent) / 100.f, 2.f);
|
||||||
SDL_UnlockAudio();
|
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)
|
return myHardwareSpec.samples;
|
||||||
myNumChannels = channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void SoundSDL2::setFrameRate(float framerate)
|
uInt32 SoundSDL2::getSampleRate() const
|
||||||
{
|
{
|
||||||
// Recalculate since frame rate has changed
|
return myHardwareSpec.freq;
|
||||||
// FIXME - should we clear out the queue or adjust the values in it?
|
|
||||||
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / framerate;
|
|
||||||
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / framerate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
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
|
for (uInt32 i = 0; i < length; i++) stream[i] = stream[i] * myVolumeFactor;
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void SoundSDL2::processFragment(Int16* stream, uInt32 length)
|
void SoundSDL2::initResampler()
|
||||||
{
|
{
|
||||||
uInt32 channels = myHardwareSpec.channels;
|
Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
|
||||||
length = length / channels;
|
Int16* nextFragment = nullptr;
|
||||||
|
|
||||||
// If there are excessive items on the queue then we'll remove some
|
if (myUnderrun)
|
||||||
if(myRegWriteQueue.duration() > myFragmentSizeLogDiv1)
|
nextFragment = myAudioQueue->size() > myEmulationTiming->prebufferFragmentCount() ?
|
||||||
{
|
myAudioQueue->dequeue(myCurrentFragment) : nullptr;
|
||||||
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;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
nextFragment = myAudioQueue->dequeue(myCurrentFragment);
|
||||||
// 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();
|
|
||||||
|
|
||||||
// How long will the remaining samples in the fragment take to play
|
myUnderrun = nextFragment == nullptr;
|
||||||
double duration = remaining / myHardwareSpec.freq;
|
if (nextFragment) myCurrentFragment = nextFragment;
|
||||||
|
|
||||||
// Does the register update occur before the end of the fragment?
|
return nextFragment;
|
||||||
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)));
|
|
||||||
|
|
||||||
position += samples;
|
Resampler::Format formatFrom =
|
||||||
remaining -= samples;
|
Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo());
|
||||||
}
|
Resampler::Format formatTo =
|
||||||
myTIASound.set(info.addr, info.value);
|
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1);
|
||||||
myRegWriteQueue.dequeue();
|
|
||||||
}
|
|
||||||
else
|
switch (myAudioSettings.resamplingQuality()) {
|
||||||
{
|
case AudioSettings::ResamplingQuality::nearestNeightbour:
|
||||||
// The next register update occurs in the next fragment so finish
|
myResampler = make_unique<SimpleResampler>(formatFrom, formatTo, nextFragmentCallback);
|
||||||
// this fragment with the current TIA settings and reduce the register
|
(cerr << "resampling quality 1: using nearest neighbor resampling\n").flush();
|
||||||
// update delay by the corresponding amount of time
|
break;
|
||||||
myTIASound.process(stream + (uInt32(position) * channels),
|
|
||||||
length - uInt32(position));
|
case AudioSettings::ResamplingQuality::lanczos_2:
|
||||||
info.delta -= duration;
|
(cerr << "resampling quality 2: using nearest Lanczos resampling, a = 2\n").flush();
|
||||||
break;
|
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)
|
void SoundSDL2::callback(void* udata, uInt8* stream, int len)
|
||||||
{
|
{
|
||||||
SoundSDL2* sound = static_cast<SoundSDL2*>(udata);
|
SoundSDL2* self = static_cast<SoundSDL2*>(udata);
|
||||||
if(sound->myIsEnabled)
|
|
||||||
{
|
if (self->myAudioQueue)
|
||||||
// The callback is requesting 8-bit (unsigned) data, but the TIA sound
|
self->processFragment(reinterpret_cast<float*>(stream), len >> 2);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
SDL_memset(stream, 0, len); // Write 'silence'
|
SDL_memset(stream, 0, len);
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // SOUND_SUPPORT
|
#endif // SOUND_SUPPORT
|
||||||
|
|
|
@ -21,17 +21,20 @@
|
||||||
#define SOUND_SDL2_HXX
|
#define SOUND_SDL2_HXX
|
||||||
|
|
||||||
class OSystem;
|
class OSystem;
|
||||||
|
class AudioQueue;
|
||||||
|
class EmulationTiming;
|
||||||
|
class AudioSettings;
|
||||||
|
|
||||||
#include "SDL_lib.hxx"
|
#include "SDL_lib.hxx"
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "TIASnd.hxx"
|
|
||||||
#include "Sound.hxx"
|
#include "Sound.hxx"
|
||||||
|
#include "audio/Resampler.hxx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class implements the sound API for SDL.
|
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
|
class SoundSDL2 : public Sound
|
||||||
{
|
{
|
||||||
|
@ -40,7 +43,7 @@ class SoundSDL2 : public Sound
|
||||||
Create a new sound object. The init method must be invoked before
|
Create a new sound object. The init method must be invoked before
|
||||||
using the object.
|
using the object.
|
||||||
*/
|
*/
|
||||||
SoundSDL2(OSystem& osystem);
|
SoundSDL2(OSystem& osystem, AudioSettings& audioSettings);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Destructor
|
Destructor
|
||||||
|
@ -51,34 +54,15 @@ class SoundSDL2 : public Sound
|
||||||
/**
|
/**
|
||||||
Enables/disables the sound subsystem.
|
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;
|
void setEnabled(bool enable) 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializes the sound device. This must be called before any
|
Initializes the sound device. This must be called before any
|
||||||
calls are made to derived methods.
|
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
|
Should be called to close the sound device. Once called the sound
|
||||||
|
@ -98,15 +82,6 @@ class SoundSDL2 : public Sound
|
||||||
*/
|
*/
|
||||||
void reset() override;
|
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
|
Sets the volume of the sound device to the specified level. The
|
||||||
volume is given as a percentage from 0 to 100. Values outside
|
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
|
@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.
|
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;
|
void adjustVolume(Int8 direction) override;
|
||||||
|
|
||||||
public:
|
uInt32 getFragmentSize() const override;
|
||||||
/**
|
|
||||||
Saves the current state of this device to the given Serializer.
|
|
||||||
|
|
||||||
@param out The serializer device to save to.
|
uInt32 getSampleRate() const override;
|
||||||
@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"; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
|
@ -157,124 +112,33 @@ class SoundSDL2 : public Sound
|
||||||
@param stream Pointer to the start of the fragment
|
@param stream Pointer to the start of the fragment
|
||||||
@param length Length of the fragment
|
@param length Length of the fragment
|
||||||
*/
|
*/
|
||||||
void processFragment(Int16* stream, uInt32 length);
|
void processFragment(float* 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// TIASound emulation object
|
|
||||||
TIASound myTIASound;
|
|
||||||
|
|
||||||
// Indicates if the sound subsystem is to be initialized
|
void initResampler();
|
||||||
bool myIsEnabled;
|
|
||||||
|
|
||||||
|
private:
|
||||||
// Indicates if the sound device was successfully initialized
|
// Indicates if the sound device was successfully initialized
|
||||||
bool myIsInitializedFlag;
|
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)
|
// Current volume as a percentage (0 - 100)
|
||||||
uInt32 myVolume;
|
uInt32 myVolume;
|
||||||
|
float myVolumeFactor;
|
||||||
|
|
||||||
// Audio specification structure
|
// Audio specification structure
|
||||||
SDL_AudioSpec myHardwareSpec;
|
SDL_AudioSpec myHardwareSpec;
|
||||||
|
|
||||||
// Queue of TIA register writes
|
shared_ptr<AudioQueue> myAudioQueue;
|
||||||
RegWriteQueue myRegWriteQueue;
|
|
||||||
|
EmulationTiming* myEmulationTiming;
|
||||||
|
|
||||||
|
Int16* myCurrentFragment;
|
||||||
|
bool myUnderrun;
|
||||||
|
|
||||||
|
unique_ptr<Resampler> myResampler;
|
||||||
|
|
||||||
|
AudioSettings& myAudioSettings;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Callback function invoked by the SDL Audio library when it needs data
|
// Callback function invoked by the SDL Audio library when it needs data
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
#ifndef VERSION_HXX
|
#ifndef VERSION_HXX
|
||||||
#define VERSION_HXX
|
#define VERSION_HXX
|
||||||
|
|
||||||
#define STELLA_VERSION "5.2_pre"
|
#define STELLA_VERSION "5.2_soundtest-1"
|
||||||
#define STELLA_BUILD "4138"
|
#define STELLA_BUILD "4138"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -15,7 +15,9 @@ MODULE_OBJS := \
|
||||||
src/common/RewindManager.o \
|
src/common/RewindManager.o \
|
||||||
src/common/SoundSDL2.o \
|
src/common/SoundSDL2.o \
|
||||||
src/common/StateManager.o \
|
src/common/StateManager.o \
|
||||||
src/common/ZipHandler.o
|
src/common/ZipHandler.o \
|
||||||
|
src/common/AudioQueue.o \
|
||||||
|
src/common/AudioSettings.o
|
||||||
|
|
||||||
MODULE_DIRS += \
|
MODULE_DIRS += \
|
||||||
src/common
|
src/common
|
||||||
|
|
|
@ -513,7 +513,7 @@ void Debugger::nextFrame(int frames)
|
||||||
unlockSystem();
|
unlockSystem();
|
||||||
while(frames)
|
while(frames)
|
||||||
{
|
{
|
||||||
myOSystem.console().tia().update();
|
myOSystem.console().tia().update(myOSystem.console().emulationTiming().maxCyclesPerTimeslice());
|
||||||
--frames;
|
--frames;
|
||||||
}
|
}
|
||||||
lockSystem();
|
lockSystem();
|
||||||
|
|
|
@ -168,6 +168,8 @@ void TiaOutputWidget::drawWidget(bool hilite)
|
||||||
uInt32 scanx, scany, scanoffset;
|
uInt32 scanx, scany, scanoffset;
|
||||||
bool visible = instance().console().tia().electronBeamPos(scanx, scany);
|
bool visible = instance().console().tia().electronBeamPos(scanx, scany);
|
||||||
scanoffset = width * scany + scanx;
|
scanoffset = width * scany + scanx;
|
||||||
|
uInt8* tiaOutputBuffer = instance().console().tia().outputBuffer();
|
||||||
|
TIASurface& tiaSurface(instance().frameBuffer().tiaSurface());
|
||||||
|
|
||||||
for(uInt32 y = 0, i = 0; y < height; ++y)
|
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)
|
for(uInt32 x = 0; x < width; ++x, ++i)
|
||||||
{
|
{
|
||||||
uInt8 shift = i >= scanoffset ? 1 : 0;
|
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;
|
||||||
*line_ptr++ = pixel;
|
*line_ptr++ = pixel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,8 @@
|
||||||
#include "Version.hxx"
|
#include "Version.hxx"
|
||||||
#include "TIAConstants.hxx"
|
#include "TIAConstants.hxx"
|
||||||
#include "FrameLayout.hxx"
|
#include "FrameLayout.hxx"
|
||||||
|
#include "AudioQueue.hxx"
|
||||||
|
#include "AudioSettings.hxx"
|
||||||
#include "frame-manager/FrameManager.hxx"
|
#include "frame-manager/FrameManager.hxx"
|
||||||
#include "frame-manager/FrameLayoutDetector.hxx"
|
#include "frame-manager/FrameLayoutDetector.hxx"
|
||||||
#include "frame-manager/YStartDetector.hxx"
|
#include "frame-manager/YStartDetector.hxx"
|
||||||
|
@ -75,17 +77,17 @@ namespace {
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
||||||
const Properties& props)
|
const Properties& props, AudioSettings& audioSettings)
|
||||||
: myOSystem(osystem),
|
: myOSystem(osystem),
|
||||||
myEvent(osystem.eventHandler().event()),
|
myEvent(osystem.eventHandler().event()),
|
||||||
myProperties(props),
|
myProperties(props),
|
||||||
myCart(std::move(cart)),
|
myCart(std::move(cart)),
|
||||||
myDisplayFormat(""), // Unknown TV format @ start
|
myDisplayFormat(""), // Unknown TV format @ start
|
||||||
myFramerate(0.0), // Unknown framerate @ start
|
|
||||||
myCurrentFormat(0), // Unknown format @ start,
|
myCurrentFormat(0), // Unknown format @ start,
|
||||||
myAutodetectedYstart(0),
|
myAutodetectedYstart(0),
|
||||||
myUserPaletteDefined(false),
|
myUserPaletteDefined(false),
|
||||||
myConsoleTiming(ConsoleTiming::ntsc)
|
myConsoleTiming(ConsoleTiming::ntsc),
|
||||||
|
myAudioSettings(audioSettings)
|
||||||
{
|
{
|
||||||
// Load user-defined palette for this ROM
|
// Load user-defined palette for this ROM
|
||||||
loadUserPalette();
|
loadUserPalette();
|
||||||
|
@ -93,7 +95,7 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
||||||
// Create subsystems for the console
|
// Create subsystems for the console
|
||||||
my6502 = make_unique<M6502>(myOSystem.settings());
|
my6502 = make_unique<M6502>(myOSystem.settings());
|
||||||
myRiot = make_unique<M6532>(*this, 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>();
|
myFrameManager = make_unique<FrameManager>();
|
||||||
mySwitches = make_unique<Switches>(myEvent, myProperties, myOSystem.settings());
|
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
|
// 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
|
// 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
|
// properties (60Hz, 262 scanlines, etc), but likely result in flicker
|
||||||
// The TIA will self-adjust the framerate if necessary
|
|
||||||
setTIAProperties();
|
setTIAProperties();
|
||||||
if(myDisplayFormat == "NTSC")
|
if(myDisplayFormat == "NTSC")
|
||||||
{
|
{
|
||||||
|
@ -205,6 +206,10 @@ Console::~Console()
|
||||||
// Some smart controllers need to be informed that the console is going away
|
// Some smart controllers need to be informed that the console is going away
|
||||||
myLeftControl->close();
|
myLeftControl->close();
|
||||||
myRightControl->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();
|
setTIAProperties();
|
||||||
myTIA->frameReset();
|
myTIA->frameReset();
|
||||||
initializeVideo(); // takes care of refreshing the screen
|
initializeVideo(); // takes care of refreshing the screen
|
||||||
|
initializeAudio(); // ensure that audio synthesis is set up to match emulation speed
|
||||||
}
|
}
|
||||||
|
|
||||||
myOSystem.frameBuffer().showMessage(message);
|
myOSystem.frameBuffer().showMessage(message);
|
||||||
|
@ -569,37 +575,29 @@ FBInitStatus Console::initializeVideo(bool full)
|
||||||
}
|
}
|
||||||
setPalette(myOSystem.settings().getString("palette"));
|
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;
|
return fbstatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void Console::initializeAudio()
|
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().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
|
myEmulationTiming
|
||||||
myTIA->enableAutoFrame(framerate <= 0);
|
.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.
|
/* Original frying research and code by Fred Quimby.
|
||||||
|
@ -729,16 +727,11 @@ void Console::setTIAProperties()
|
||||||
myDisplayFormat == "SECAM60")
|
myDisplayFormat == "SECAM60")
|
||||||
{
|
{
|
||||||
// Assume we've got ~262 scanlines (NTSC-like format)
|
// Assume we've got ~262 scanlines (NTSC-like format)
|
||||||
myFramerate = 60.0;
|
|
||||||
myConsoleInfo.InitialFrameRate = "60";
|
|
||||||
myTIA->setLayout(FrameLayout::ntsc);
|
myTIA->setLayout(FrameLayout::ntsc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Assume we've got ~312 scanlines (PAL-like format)
|
// Assume we've got ~312 scanlines (PAL-like format)
|
||||||
myFramerate = 50.0;
|
|
||||||
myConsoleInfo.InitialFrameRate = "50";
|
|
||||||
|
|
||||||
// PAL ROMs normally need at least 250 lines
|
// PAL ROMs normally need at least 250 lines
|
||||||
if (height != 0) height = std::max(height, 250u);
|
if (height != 0) height = std::max(height, 250u);
|
||||||
|
|
||||||
|
@ -747,6 +740,18 @@ void Console::setTIAProperties()
|
||||||
|
|
||||||
myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart);
|
myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart);
|
||||||
myTIA->setHeight(height);
|
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;
|
return myTIA->frameBufferFrameRate();
|
||||||
myOSystem.setFramerate(framerate);
|
|
||||||
myOSystem.sound().setFrameRate(framerate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -1103,7 +1106,7 @@ uInt32 Console::ourPALPalette[256] = {
|
||||||
0x001759, 0, 0x00247c, 0, 0x1d469e, 0, 0x3f68c0, 0, // 288 b
|
0x001759, 0, 0x00247c, 0, 0x1d469e, 0, 0x3f68c0, 0, // 288 b
|
||||||
0x618ae2, 0, 0x83acff, 0, 0xa5ceff, 0, 0xc7f0ff, 0,
|
0x618ae2, 0, 0x83acff, 0, 0xa5ceff, 0, 0xc7f0ff, 0,
|
||||||
0x12006d, 0, 0x34038f, 0, 0x5625b1, 0, 0x7847d3, 0, // 2a0 c
|
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
|
0x000070, 0, 0x161292, 0, 0x3834b4, 0, 0x5a56d6, 0, // 2b8 d
|
||||||
0x7c78f8, 0, 0x9e9aff, 0, 0xc0bcff, 0, 0xe2deff, 0,
|
0x7c78f8, 0, 0x9e9aff, 0, 0xc0bcff, 0, 0xe2deff, 0,
|
||||||
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2d0 e
|
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2d0 e
|
||||||
|
|
|
@ -27,6 +27,8 @@ class M6532;
|
||||||
class Cartridge;
|
class Cartridge;
|
||||||
class CompuMate;
|
class CompuMate;
|
||||||
class Debugger;
|
class Debugger;
|
||||||
|
class AudioQueue;
|
||||||
|
class AudioSettings;
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "Control.hxx"
|
#include "Control.hxx"
|
||||||
|
@ -36,6 +38,7 @@ class Debugger;
|
||||||
#include "Serializable.hxx"
|
#include "Serializable.hxx"
|
||||||
#include "EventHandlerConstants.hxx"
|
#include "EventHandlerConstants.hxx"
|
||||||
#include "NTSCFilter.hxx"
|
#include "NTSCFilter.hxx"
|
||||||
|
#include "EmulationTiming.hxx"
|
||||||
#include "frame-manager/AbstractFrameManager.hxx"
|
#include "frame-manager/AbstractFrameManager.hxx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +52,6 @@ struct ConsoleInfo
|
||||||
string Control0;
|
string Control0;
|
||||||
string Control1;
|
string Control1;
|
||||||
string DisplayFormat;
|
string DisplayFormat;
|
||||||
string InitialFrameRate;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +81,7 @@ class Console : public Serializable
|
||||||
@param props The properties for the cartridge
|
@param props The properties for the cartridge
|
||||||
*/
|
*/
|
||||||
Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
||||||
const Properties& props);
|
const Properties& props, AudioSettings& audioSettings);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Destructor
|
Destructor
|
||||||
|
@ -190,6 +192,11 @@ class Console : public Serializable
|
||||||
*/
|
*/
|
||||||
void stateChanged(EventHandlerState state);
|
void stateChanged(EventHandlerState state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Retrieve emulation timing provider.
|
||||||
|
*/
|
||||||
|
EmulationTiming& emulationTiming() { return myEmulationTiming; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
Toggle between NTSC/PAL/SECAM (and variants) display format.
|
Toggle between NTSC/PAL/SECAM (and variants) display format.
|
||||||
|
@ -270,16 +277,9 @@ class Console : public Serializable
|
||||||
void changeHeight(int direction);
|
void changeHeight(int direction);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sets the framerate of the console, which in turn communicates
|
Returns the current framerate.
|
||||||
this to all applicable subsystems.
|
|
||||||
*/
|
*/
|
||||||
void setFramerate(float framerate);
|
float getFramerate() const;
|
||||||
|
|
||||||
/**
|
|
||||||
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; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Toggles the TIA bit specified in the method name.
|
Toggles the TIA bit specified in the method name.
|
||||||
|
@ -330,6 +330,11 @@ class Console : public Serializable
|
||||||
*/
|
*/
|
||||||
void setTIAProperties();
|
void setTIAProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create the audio queue
|
||||||
|
*/
|
||||||
|
void createAudioQueue();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds the left and right controllers to the console.
|
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.
|
// The frame manager instance that is used during emulation.
|
||||||
unique_ptr<AbstractFrameManager> myFrameManager;
|
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)
|
// Pointer to the Cartridge (the debugger needs it)
|
||||||
unique_ptr<Cartridge> myCart;
|
unique_ptr<Cartridge> myCart;
|
||||||
|
|
||||||
|
@ -404,9 +412,6 @@ class Console : public Serializable
|
||||||
// The currently defined display format (NTSC/PAL/SECAM)
|
// The currently defined display format (NTSC/PAL/SECAM)
|
||||||
string myDisplayFormat;
|
string myDisplayFormat;
|
||||||
|
|
||||||
// The currently defined display framerate
|
|
||||||
float myFramerate;
|
|
||||||
|
|
||||||
// Display format currently in use
|
// Display format currently in use
|
||||||
uInt32 myCurrentFormat;
|
uInt32 myCurrentFormat;
|
||||||
|
|
||||||
|
@ -423,6 +428,13 @@ class Console : public Serializable
|
||||||
// Contains timing information for this console
|
// Contains timing information for this console
|
||||||
ConsoleTiming myConsoleTiming;
|
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
|
// Table of RGB values for NTSC, PAL and SECAM
|
||||||
static uInt32 ourNTSCPalette[256];
|
static uInt32 ourNTSCPalette[256];
|
||||||
static uInt32 ourPALPalette[256];
|
static uInt32 ourPALPalette[256];
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -48,13 +48,9 @@ FrameBuffer::FrameBuffer(OSystem& osystem)
|
||||||
myPausedCount(0),
|
myPausedCount(0),
|
||||||
myStatsEnabled(false),
|
myStatsEnabled(false),
|
||||||
myLastScanlines(0),
|
myLastScanlines(0),
|
||||||
myLastFrameRate(60),
|
|
||||||
myGrabMouse(false),
|
myGrabMouse(false),
|
||||||
myCurrentModeList(nullptr),
|
myCurrentModeList(nullptr)
|
||||||
myTotalTime(0),
|
{}
|
||||||
myTotalFrames(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
FrameBuffer::~FrameBuffer()
|
FrameBuffer::~FrameBuffer()
|
||||||
|
@ -272,29 +268,8 @@ void FrameBuffer::update()
|
||||||
switch(myOSystem.eventHandler().state())
|
switch(myOSystem.eventHandler().state())
|
||||||
{
|
{
|
||||||
case EventHandlerState::EMULATION:
|
case EventHandlerState::EMULATION:
|
||||||
{
|
// Do nothing; emulation mode is handled separately (see below)
|
||||||
// Run the console for one frame
|
break;
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
case EventHandlerState::PAUSE:
|
case EventHandlerState::PAUSE:
|
||||||
{
|
{
|
||||||
|
@ -356,6 +331,30 @@ void FrameBuffer::update()
|
||||||
postFrameUpdate();
|
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,
|
void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
||||||
bool force)
|
bool force)
|
||||||
|
@ -367,6 +366,7 @@ void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
||||||
// Precompute the message coordinates
|
// Precompute the message coordinates
|
||||||
myMsg.text = message;
|
myMsg.text = message;
|
||||||
myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds
|
myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds
|
||||||
|
if(myMsg.counter == 0) myMsg.counter = 60;
|
||||||
myMsg.color = kBtnTextColor;
|
myMsg.color = kBtnTextColor;
|
||||||
|
|
||||||
myMsg.w = font().getStringWidth(myMsg.text) + 10;
|
myMsg.w = font().getStringWidth(myMsg.text) + 10;
|
||||||
|
@ -389,9 +389,9 @@ void FrameBuffer::drawFrameStats()
|
||||||
myStatsMsg.surface->invalidate();
|
myStatsMsg.surface->invalidate();
|
||||||
|
|
||||||
// draw scanlines
|
// draw scanlines
|
||||||
color = myOSystem.console().tia().scanlinesLastFrame() != myLastScanlines ?
|
color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ?
|
||||||
uInt32(kDbgColorRed) : myStatsMsg.color;
|
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.surface->drawString(font(), msg, xPos, YPOS,
|
||||||
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
|
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
|
||||||
xPos += font().getStringWidth(msg);
|
xPos += font().getStringWidth(msg);
|
||||||
|
@ -402,23 +402,10 @@ void FrameBuffer::drawFrameStats()
|
||||||
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
||||||
xPos += font().getStringWidth(msg);
|
xPos += font().getStringWidth(msg);
|
||||||
|
|
||||||
// draw the effective framerate
|
std::snprintf(msg, 30, " @ %5.2ffps", myOSystem.console().getFramerate());
|
||||||
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);
|
|
||||||
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
|
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
|
||||||
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
||||||
myLastFrameRate = frameRate;
|
|
||||||
|
|
||||||
// draw bankswitching type
|
// draw bankswitching type
|
||||||
string bsinfo = info.BankSwitch +
|
string bsinfo = info.BankSwitch +
|
||||||
|
@ -751,8 +738,7 @@ void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight)
|
||||||
myDesktopSize.w, myDesktopSize.h);
|
myDesktopSize.w, myDesktopSize.h);
|
||||||
|
|
||||||
// Aspect ratio
|
// Aspect ratio
|
||||||
bool ntsc = myOSystem.console().about().InitialFrameRate == "60";
|
uInt32 aspect = myOSystem.settings().getInt(myOSystem.console().timing() == ConsoleTiming::ntsc ?
|
||||||
uInt32 aspect = myOSystem.settings().getInt(ntsc ?
|
|
||||||
"tia.aspectn" : "tia.aspectp");
|
"tia.aspectn" : "tia.aspectp");
|
||||||
|
|
||||||
// Figure our the smallest zoom level we can use
|
// Figure our the smallest zoom level we can use
|
||||||
|
|
|
@ -113,10 +113,16 @@ class FrameBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Updates the display, which depending on the current mode could mean
|
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();
|
void update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
There is a dedicated update method for emulation mode.
|
||||||
|
*/
|
||||||
|
void updateInEmulationMode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Shows a message onscreen.
|
Shows a message onscreen.
|
||||||
|
|
||||||
|
@ -522,7 +528,6 @@ class FrameBuffer
|
||||||
Message myStatsMsg;
|
Message myStatsMsg;
|
||||||
bool myStatsEnabled;
|
bool myStatsEnabled;
|
||||||
uInt32 myLastScanlines;
|
uInt32 myLastScanlines;
|
||||||
float myLastFrameRate;
|
|
||||||
|
|
||||||
bool myGrabMouse;
|
bool myGrabMouse;
|
||||||
|
|
||||||
|
@ -540,9 +545,6 @@ class FrameBuffer
|
||||||
// Holds UI palette data (standard and classic colours)
|
// Holds UI palette data (standard and classic colours)
|
||||||
static uInt32 ourGUIColors[3][kNumColors-256];
|
static uInt32 ourGUIColors[3][kNumColors-256];
|
||||||
|
|
||||||
uInt64 myTotalTime;
|
|
||||||
uInt64 myTotalFrames;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
FrameBuffer() = delete;
|
FrameBuffer() = delete;
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
#include "System.hxx"
|
#include "System.hxx"
|
||||||
#include "M6502.hxx"
|
#include "M6502.hxx"
|
||||||
|
#include "DispatchResult.hxx"
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
M6502::M6502(const Settings& settings)
|
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
|
#ifdef DEBUGGER_SUPPORT
|
||||||
// Debugger hack: this ensures that stepping a "STA WSYNC" will actually end at the
|
// 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
|
// the halt to take effect). This is safe because as we know that the next cycle will be a read
|
||||||
// cycle anyway.
|
// cycle anyway.
|
||||||
handleHalt();
|
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
|
#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
|
// Clear all of the execution status bits except for the fatal error bit
|
||||||
myExecutionStatus &= FatalErrorBit;
|
myExecutionStatus &= FatalErrorBit;
|
||||||
|
@ -239,32 +249,43 @@ inline bool M6502::_execute(uInt32 number)
|
||||||
M6532& riot = mySystem->m6532();
|
M6532& riot = mySystem->m6532();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
uInt64 previousCycles = mySystem->cycles();
|
||||||
|
uInt32 currentCycles = 0;
|
||||||
|
|
||||||
// Loop until execution is stopped or a fatal error occurs
|
// Loop until execution is stopped or a fatal error occurs
|
||||||
for(;;)
|
for(;;)
|
||||||
{
|
{
|
||||||
for(; !myExecutionStatus && (number != 0); --number)
|
while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU)
|
||||||
{
|
{
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
|
// Don't break if we haven't actually executed anything yet
|
||||||
{
|
if (currentCycles > 0) {
|
||||||
bool read = myJustHitReadTrapFlag;
|
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
|
||||||
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
|
{
|
||||||
|
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))
|
int cond = evalCondSaveStates();
|
||||||
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();
|
|
||||||
if(cond > -1)
|
if(cond > -1)
|
||||||
{
|
{
|
||||||
stringstream msg;
|
stringstream msg;
|
||||||
|
@ -294,6 +315,8 @@ inline bool M6502::_execute(uInt32 number)
|
||||||
myExecutionStatus |= FatalErrorBit;
|
myExecutionStatus |= FatalErrorBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentCycles = (mySystem->cycles() - previousCycles);
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
if(myStepStateByInstruction)
|
if(myStepStateByInstruction)
|
||||||
{
|
{
|
||||||
|
@ -318,21 +341,24 @@ inline bool M6502::_execute(uInt32 number)
|
||||||
if(myExecutionStatus & StopExecutionBit)
|
if(myExecutionStatus & StopExecutionBit)
|
||||||
{
|
{
|
||||||
// Yes, so answer that everything finished fine
|
// Yes, so answer that everything finished fine
|
||||||
return true;
|
result.setOk(currentCycles);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if a fatal error has occured
|
// See if a fatal error has occured
|
||||||
if(myExecutionStatus & FatalErrorBit)
|
if(myExecutionStatus & FatalErrorBit)
|
||||||
{
|
{
|
||||||
// Yes, so answer that something when wrong
|
// Yes, so answer that something when wrong
|
||||||
return false;
|
result.setFatal(currentCycles + icycles);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we've executed the specified number of instructions
|
// 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
|
// Yes, so answer that everything finished fine
|
||||||
return true;
|
result.setOk(currentCycles);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -606,15 +632,4 @@ void M6502::updateStepStateByInstruction()
|
||||||
myTrapConds.size();
|
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
|
#endif // DEBUGGER_SUPPORT
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
class System;
|
class System;
|
||||||
|
class DispatchResult;
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
class Debugger;
|
class Debugger;
|
||||||
|
@ -110,10 +111,14 @@ class M6502 : public Serializable
|
||||||
is executed, someone stops execution, or an error occurs. Answers
|
is executed, someone stops execution, or an error occurs. Answers
|
||||||
true iff execution stops normally.
|
true iff execution stops normally.
|
||||||
|
|
||||||
@param number Indicates the number of instructions to execute
|
@param cycles Indicates the number of cycles to execute. Not that the actual
|
||||||
@return true iff execution stops normally
|
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
|
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
|
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.
|
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
|
#ifdef DEBUGGER_SUPPORT
|
||||||
/**
|
/**
|
||||||
|
@ -326,12 +331,6 @@ class M6502 : public Serializable
|
||||||
with the CPU and update the flag accordingly.
|
with the CPU and update the flag accordingly.
|
||||||
*/
|
*/
|
||||||
void updateStepStateByInstruction();
|
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
|
#endif // DEBUGGER_SUPPORT
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -17,11 +17,6 @@
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <ctime>
|
|
||||||
#ifdef HAVE_GETTIMEOFDAY
|
|
||||||
#include <sys/time.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
|
|
||||||
#include "MediaFactory.hxx"
|
#include "MediaFactory.hxx"
|
||||||
|
@ -35,6 +30,8 @@
|
||||||
#include "CheatManager.hxx"
|
#include "CheatManager.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
#include "MD5.hxx"
|
#include "MD5.hxx"
|
||||||
#include "Cart.hxx"
|
#include "Cart.hxx"
|
||||||
|
@ -55,17 +52,19 @@
|
||||||
#include "SerialPort.hxx"
|
#include "SerialPort.hxx"
|
||||||
#include "StateManager.hxx"
|
#include "StateManager.hxx"
|
||||||
#include "Version.hxx"
|
#include "Version.hxx"
|
||||||
|
#include "TIA.hxx"
|
||||||
|
#include "DispatchResult.hxx"
|
||||||
|
#include "EmulationWorker.hxx"
|
||||||
|
|
||||||
#include "OSystem.hxx"
|
#include "OSystem.hxx"
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
OSystem::OSystem()
|
OSystem::OSystem()
|
||||||
: myLauncherUsed(false),
|
: myLauncherUsed(false),
|
||||||
myQuitLoop(false)
|
myQuitLoop(false)
|
||||||
{
|
{
|
||||||
// Calculate startup time
|
|
||||||
myMillisAtStart = uInt32(time(nullptr) * 1000);
|
|
||||||
|
|
||||||
// Get built-in features
|
// Get built-in features
|
||||||
#ifdef SOUND_SUPPORT
|
#ifdef SOUND_SUPPORT
|
||||||
myFeatures += "Sound ";
|
myFeatures += "Sound ";
|
||||||
|
@ -91,6 +90,7 @@ OSystem::OSystem()
|
||||||
myBuildInfo = info.str();
|
myBuildInfo = info.str();
|
||||||
|
|
||||||
mySettings = MediaFactory::createSettings(*this);
|
mySettings = MediaFactory::createSettings(*this);
|
||||||
|
myAudioSettings = AudioSettings(mySettings.get());
|
||||||
myRandom = make_unique<Random>(*this);
|
myRandom = make_unique<Random>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,16 +283,6 @@ void OSystem::setConfigFile(const string& file)
|
||||||
myConfigFile = FilesystemNode(file).getPath();
|
myConfigFile = FilesystemNode(file).getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void OSystem::setFramerate(float framerate)
|
|
||||||
{
|
|
||||||
if(framerate > 0.0)
|
|
||||||
{
|
|
||||||
myDisplayFrameRate = framerate;
|
|
||||||
myTimePerFrame = uInt32(1000000.0 / myDisplayFrameRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
FBInitStatus OSystem::createFrameBuffer()
|
FBInitStatus OSystem::createFrameBuffer()
|
||||||
{
|
{
|
||||||
|
@ -332,9 +322,9 @@ FBInitStatus OSystem::createFrameBuffer()
|
||||||
void OSystem::createSound()
|
void OSystem::createSound()
|
||||||
{
|
{
|
||||||
if(!mySound)
|
if(!mySound)
|
||||||
mySound = MediaFactory::createAudio(*this);
|
mySound = MediaFactory::createAudio(*this, myAudioSettings);
|
||||||
#ifndef SOUND_SUPPORT
|
#ifndef SOUND_SUPPORT
|
||||||
mySettings->setValue("sound", false);
|
myAudioSettings.setEnabled(false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,9 +398,6 @@ string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
|
||||||
<< getROMInfo(*myConsole) << endl;
|
<< getROMInfo(*myConsole) << endl;
|
||||||
logMessage(buf.str(), 1);
|
logMessage(buf.str(), 1);
|
||||||
|
|
||||||
// Update the timing info for a new console run
|
|
||||||
resetLoopTiming();
|
|
||||||
|
|
||||||
myFrameBuffer->setCursorState();
|
myFrameBuffer->setCursorState();
|
||||||
|
|
||||||
// Also check if certain virtual buttons should be held down
|
// Also check if certain virtual buttons should be held down
|
||||||
|
@ -450,8 +437,6 @@ bool OSystem::createLauncher(const string& startdir)
|
||||||
myLauncher->reStack();
|
myLauncher->reStack();
|
||||||
myFrameBuffer->setCursorState();
|
myFrameBuffer->setCursorState();
|
||||||
|
|
||||||
setFramerate(30);
|
|
||||||
resetLoopTiming();
|
|
||||||
status = true;
|
status = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -565,7 +550,7 @@ unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string&
|
||||||
|
|
||||||
// Finally, create the cart with the correct properties
|
// Finally, create the cart with the correct properties
|
||||||
if(cart)
|
if(cart)
|
||||||
console = make_unique<Console>(*this, cart, props);
|
console = make_unique<Console>(*this, cart, props, myAudioSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
return console;
|
return console;
|
||||||
|
@ -627,15 +612,6 @@ string OSystem::getROMInfo(const Console& console)
|
||||||
return buf.str();
|
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,
|
void OSystem::validatePath(string& path, const string& setting,
|
||||||
const string& defaultpath)
|
const string& defaultpath)
|
||||||
|
@ -653,68 +629,105 @@ void OSystem::validatePath(string& path, const string& setting,
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
uInt64 OSystem::getTicks() const
|
uInt64 OSystem::getTicks() const
|
||||||
{
|
{
|
||||||
#ifdef HAVE_GETTIMEOFDAY
|
return duration_cast<duration<uInt64, std::ratio<1, 1000000> > >(system_clock::now().time_since_epoch()).count();
|
||||||
// Gettimeofday natively refers to the UNIX epoch (a set time in the past)
|
}
|
||||||
timeval now;
|
|
||||||
gettimeofday(&now, nullptr);
|
|
||||||
|
|
||||||
return uInt64(now.tv_sec) * 1000000 + now.tv_usec;
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
#else
|
float OSystem::frameRate() const
|
||||||
// We use SDL_GetTicks, but add in the time when the application was
|
{
|
||||||
// initialized. This is necessary, since SDL_GetTicks only measures how
|
return myConsole ? myConsole->getFramerate() : 0;
|
||||||
// long SDL has been running, which can be the same between multiple runs
|
}
|
||||||
// of the application.
|
|
||||||
return uInt64(SDL_GetTicks() + myMillisAtStart) * 1000;
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
#endif
|
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()
|
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
|
myEventHandler->poll(getTicks());
|
||||||
for(;;)
|
if(myQuitLoop) break; // Exit if the user wants to quit
|
||||||
{
|
|
||||||
myTimingInfo.start = getTicks();
|
double timesliceSeconds;
|
||||||
myEventHandler->poll(myTimingInfo.start);
|
|
||||||
if(myQuitLoop) break; // Exit if the user wants to quit
|
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();
|
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)
|
duration<double> timeslice(timesliceSeconds);
|
||||||
; // busy-wait
|
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
|
||||||
|
time_point<high_resolution_clock> now = high_resolution_clock::now();
|
||||||
|
|
||||||
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
|
// We allow 6507 time to lag behind by one frame max
|
||||||
myTimingInfo.totalFrames++;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,19 +38,13 @@ class Settings;
|
||||||
class Sound;
|
class Sound;
|
||||||
class StateManager;
|
class StateManager;
|
||||||
class VideoDialog;
|
class VideoDialog;
|
||||||
|
class EmulationWorker;
|
||||||
|
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
#include "FrameBufferConstants.hxx"
|
#include "FrameBufferConstants.hxx"
|
||||||
#include "EventHandlerConstants.hxx"
|
#include "EventHandlerConstants.hxx"
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
|
#include "AudioSettings.hxx"
|
||||||
struct TimingInfo {
|
|
||||||
uInt64 start;
|
|
||||||
uInt64 current;
|
|
||||||
uInt64 virt;
|
|
||||||
uInt64 totalTime;
|
|
||||||
uInt64 totalFrames;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class provides an interface for accessing operating system specific
|
This class provides an interface for accessing operating system specific
|
||||||
|
@ -132,6 +126,11 @@ class OSystem
|
||||||
Console& console() const { return *myConsole; }
|
Console& console() const { return *myConsole; }
|
||||||
bool hasConsole() const;
|
bool hasConsole() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the audio settings ovject.
|
||||||
|
*/
|
||||||
|
AudioSettings& audioSettings() { return myAudioSettings; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the serial port of the system.
|
Get the serial port of the system.
|
||||||
|
|
||||||
|
@ -213,26 +212,11 @@ class OSystem
|
||||||
CheatManager& cheat() const { return *myCheatManager; }
|
CheatManager& cheat() const { return *myCheatManager; }
|
||||||
#endif
|
#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.
|
Set all config file paths for the OSystem.
|
||||||
*/
|
*/
|
||||||
void setConfigPaths();
|
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.
|
Return the default full/complete directory name for storing data.
|
||||||
*/
|
*/
|
||||||
|
@ -383,12 +367,6 @@ class OSystem
|
||||||
*/
|
*/
|
||||||
const string& logMessages() const { return myLogMessages; }
|
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:
|
public:
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// The following methods are system-specific and can be overrided in
|
// The following methods are system-specific and can be overrided in
|
||||||
|
@ -406,6 +384,8 @@ class OSystem
|
||||||
*/
|
*/
|
||||||
virtual uInt64 getTicks() const;
|
virtual uInt64 getTicks() const;
|
||||||
|
|
||||||
|
float frameRate() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This method runs the main loop. Since different platforms
|
This method runs the main loop. Since different platforms
|
||||||
may use different timing methods and/or algorithms, this method can
|
may use different timing methods and/or algorithms, this method can
|
||||||
|
@ -504,18 +484,12 @@ class OSystem
|
||||||
// The list of log messages
|
// The list of log messages
|
||||||
string myLogMessages;
|
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
|
// Indicates whether to stop the main loop
|
||||||
bool myQuitLoop;
|
bool myQuitLoop;
|
||||||
|
|
||||||
|
// Audio settings
|
||||||
|
AudioSettings myAudioSettings;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string myBaseDir;
|
string myBaseDir;
|
||||||
string myStateDir;
|
string myStateDir;
|
||||||
|
@ -536,9 +510,6 @@ class OSystem
|
||||||
string myFeatures;
|
string myFeatures;
|
||||||
string myBuildInfo;
|
string myBuildInfo;
|
||||||
|
|
||||||
// Indicates whether the main processing loop should proceed
|
|
||||||
TimingInfo myTimingInfo;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
Creates the various framebuffers/renderers available in this system.
|
Creates the various framebuffers/renderers available in this system.
|
||||||
|
@ -590,12 +561,6 @@ class OSystem
|
||||||
*/
|
*/
|
||||||
string getROMInfo(const Console& console);
|
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.
|
Validate the directory name, and create it if necessary.
|
||||||
Also, update the settings with the new name. For now, validation
|
Also, update the settings with the new name. For now, validation
|
||||||
|
@ -608,6 +573,8 @@ class OSystem
|
||||||
void validatePath(string& path, const string& setting,
|
void validatePath(string& path, const string& setting,
|
||||||
const string& defaultpath);
|
const string& defaultpath);
|
||||||
|
|
||||||
|
double dispatchEmulation(EmulationWorker& emulationWorker);
|
||||||
|
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
OSystem(const OSystem&) = delete;
|
OSystem(const OSystem&) = delete;
|
||||||
OSystem(OSystem&&) = delete;
|
OSystem(OSystem&&) = delete;
|
||||||
|
|
|
@ -115,7 +115,7 @@ class PropertiesSet
|
||||||
/**
|
/**
|
||||||
Return the size of the myExternalProps list
|
Return the size of the myExternalProps list
|
||||||
*/
|
*/
|
||||||
uInt32 size() { return myExternalProps.size(); }
|
uInt64 size() const { return myExternalProps.size(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using PropsList = std::map<string, Properties>;
|
using PropsList = std::map<string, Properties>;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include "OSystem.hxx"
|
#include "OSystem.hxx"
|
||||||
#include "Version.hxx"
|
#include "Version.hxx"
|
||||||
|
#include "AudioSettings.hxx"
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
#include "DebuggerDialog.hxx"
|
#include "DebuggerDialog.hxx"
|
||||||
|
@ -34,7 +35,7 @@ Settings::Settings(OSystem& osystem)
|
||||||
{
|
{
|
||||||
// Video-related options
|
// Video-related options
|
||||||
setInternal("video", "");
|
setInternal("video", "");
|
||||||
setInternal("framerate", "0");
|
setInternal("speed", "1.0");
|
||||||
setInternal("vsync", "true");
|
setInternal("vsync", "true");
|
||||||
setInternal("fullscreen", "false");
|
setInternal("fullscreen", "false");
|
||||||
setInternal("center", "false");
|
setInternal("center", "false");
|
||||||
|
@ -69,10 +70,14 @@ Settings::Settings(OSystem& osystem)
|
||||||
setInternal("tv.bleed", "0.0");
|
setInternal("tv.bleed", "0.0");
|
||||||
|
|
||||||
// Sound options
|
// Sound options
|
||||||
setInternal("sound", "true");
|
setInternal(AudioSettings::SETTING_ENABLED, AudioSettings::DEFAULT_ENABLED);
|
||||||
setInternal("fragsize", "512");
|
setInternal(AudioSettings::SETTING_PRESET, static_cast<int>(AudioSettings::DEFAULT_PRESET));
|
||||||
setInternal("freq", "31400");
|
setInternal(AudioSettings::SETTING_SAMPLE_RATE, AudioSettings::DEFAULT_SAMPLE_RATE);
|
||||||
setInternal("volume", "100");
|
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
|
// Input event options
|
||||||
setInternal("keymap", "");
|
setInternal("keymap", "");
|
||||||
|
@ -298,6 +303,10 @@ void Settings::validate()
|
||||||
{
|
{
|
||||||
string s;
|
string s;
|
||||||
int i;
|
int i;
|
||||||
|
float f;
|
||||||
|
|
||||||
|
f = getFloat("speed");
|
||||||
|
if (f <= 0) setInternal("speed", "1.0");
|
||||||
|
|
||||||
s = getString("timing");
|
s = getString("timing");
|
||||||
if(s != "sleep" && s != "busy") setInternal("timing", "sleep");
|
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);*/
|
if(i < 0 || i > 6) setInternal("plr.tm.horizon", 5);*/
|
||||||
|
|
||||||
#ifdef SOUND_SUPPORT
|
#ifdef SOUND_SUPPORT
|
||||||
i = getInt("volume");
|
AudioSettings::normalize(*this);
|
||||||
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");
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
i = getInt("joydeadzone");
|
i = getInt("joydeadzone");
|
||||||
|
@ -438,15 +443,19 @@ void Settings::usage() const
|
||||||
<< " -palette <standard| Use the specified color palette\n"
|
<< " -palette <standard| Use the specified color palette\n"
|
||||||
<< " z26|\n"
|
<< " z26|\n"
|
||||||
<< " user>\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"
|
<< " -timing <sleep|busy> Use the given type of wait between frames\n"
|
||||||
<< " -uimessages <1|0> Show onscreen UI messages for different events\n"
|
<< " -uimessages <1|0> Show onscreen UI messages for different events\n"
|
||||||
<< endl
|
<< endl
|
||||||
#ifdef SOUND_SUPPORT
|
#ifdef SOUND_SUPPORT
|
||||||
<< " -sound <1|0> Enable sound generation\n"
|
<< " -audio.enabled <1|0> Enable audio\n"
|
||||||
<< " -fragsize <number> The size of sound fragments (must be a power of two)\n"
|
<< " -audio.preset <1-5> Audio preset (or 1 for custom)\n"
|
||||||
<< " -freq <number> Set sound sample output frequency (11025|22050|31400|44100|48000)\n"
|
<< " -audio.sample_rate <number> Output sample rate (44100|48000|96000)\n"
|
||||||
<< " -volume <number> Set the volume (0 - 100)\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
|
<< endl
|
||||||
#endif
|
#endif
|
||||||
<< " -tia.zoom <zoom> Use the specified zoom level (windowed mode) for TIA image\n"
|
<< " -tia.zoom <zoom> Use the specified zoom level (windowed mode) for TIA image\n"
|
||||||
|
|
|
@ -19,8 +19,9 @@
|
||||||
#define SOUND_HXX
|
#define SOUND_HXX
|
||||||
|
|
||||||
class OSystem;
|
class OSystem;
|
||||||
|
class AudioQueue;
|
||||||
|
class EmulationTiming;
|
||||||
|
|
||||||
#include "Serializable.hxx"
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +30,7 @@ class OSystem;
|
||||||
|
|
||||||
@author Stephen Anthony
|
@author Stephen Anthony
|
||||||
*/
|
*/
|
||||||
class Sound : public Serializable
|
class Sound
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
@ -47,26 +48,11 @@ class Sound : public Serializable
|
||||||
*/
|
*/
|
||||||
virtual void setEnabled(bool enable) = 0;
|
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
|
Start the sound system, initializing it if necessary. This must be
|
||||||
called before any calls are made to derived methods.
|
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
|
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;
|
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.
|
Reset the sound device.
|
||||||
*/
|
*/
|
||||||
virtual void reset() = 0;
|
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
|
Sets the volume of the sound device to the specified level. The
|
||||||
volume is given as a percentage from 0 to 100. Values outside
|
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
|
@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.
|
Adjusts the volume of the sound device based on the given direction.
|
||||||
|
|
|
@ -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
|
|
||||||
};
|
|
|
@ -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
|
|
|
@ -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)
|
void TIASurface::setNTSC(NTSCFilter::Preset preset, bool show)
|
||||||
{
|
{
|
||||||
|
|
|
@ -73,10 +73,9 @@ class TIASurface
|
||||||
const FBSurface& baseSurface(GUI::Rect& rect) const;
|
const FBSurface& baseSurface(GUI::Rect& rect) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the TIA pixel associated with the given TIA buffer index,
|
Use the palette to map a single indexed pixel color. This is used by the TIA output widget.
|
||||||
shifting by the given offset (for greyscale values).
|
*/
|
||||||
*/
|
uInt32 mapIndexedPixel(uInt8 indexedColor, uInt8 shift = 0);
|
||||||
uInt32 pixel(uInt32 idx, uInt8 shift = 0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the NTSCFilter object associated with the framebuffer
|
Get the NTSCFilter object associated with the framebuffer
|
||||||
|
|
|
@ -51,8 +51,11 @@ MODULE_OBJS := \
|
||||||
src/emucore/CompuMate.o \
|
src/emucore/CompuMate.o \
|
||||||
src/emucore/Console.o \
|
src/emucore/Console.o \
|
||||||
src/emucore/Control.o \
|
src/emucore/Control.o \
|
||||||
|
src/emucore/DispatchResult.o \
|
||||||
src/emucore/Driving.o \
|
src/emucore/Driving.o \
|
||||||
src/emucore/EventHandler.o \
|
src/emucore/EventHandler.o \
|
||||||
|
src/emucore/EmulationTiming.o \
|
||||||
|
src/emucore/EmulationWorker.o \
|
||||||
src/emucore/FrameBuffer.o \
|
src/emucore/FrameBuffer.o \
|
||||||
src/emucore/FBSurface.o \
|
src/emucore/FBSurface.o \
|
||||||
src/emucore/FSNode.o \
|
src/emucore/FSNode.o \
|
||||||
|
@ -75,7 +78,6 @@ MODULE_OBJS := \
|
||||||
src/emucore/Settings.o \
|
src/emucore/Settings.o \
|
||||||
src/emucore/Switches.o \
|
src/emucore/Switches.o \
|
||||||
src/emucore/System.o \
|
src/emucore/System.o \
|
||||||
src/emucore/TIASnd.o \
|
|
||||||
src/emucore/TIASurface.o \
|
src/emucore/TIASurface.o \
|
||||||
src/emucore/Thumbulator.o
|
src/emucore/Thumbulator.o
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -26,7 +26,7 @@ PaddleReader::PaddleReader()
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void PaddleReader::reset(double timestamp)
|
void PaddleReader::reset(uInt64 timestamp)
|
||||||
{
|
{
|
||||||
myU = 0;
|
myU = 0;
|
||||||
myIsDumped = false;
|
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;
|
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);
|
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) {
|
if (consoleTiming != myConsoleTiming) {
|
||||||
setConsoleTiming(consoleTiming);
|
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 (myIsDumped) return;
|
||||||
|
|
||||||
if (myValue >= 0)
|
if (myValue >= 0)
|
||||||
myU = USUPP * (1 - (1 - myU / USUPP) *
|
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;
|
myTimestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,14 @@ class PaddleReader : public Serializable
|
||||||
|
|
||||||
public:
|
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; }
|
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).
|
Serializable methods (see that class for more information).
|
||||||
|
@ -50,7 +50,7 @@ class PaddleReader : public Serializable
|
||||||
|
|
||||||
void setConsoleTiming(ConsoleTiming timing);
|
void setConsoleTiming(ConsoleTiming timing);
|
||||||
|
|
||||||
void updateCharge(double timestamp);
|
void updateCharge(uInt64 timestamp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class PaddleReader : public Serializable
|
||||||
double myU;
|
double myU;
|
||||||
|
|
||||||
double myValue;
|
double myValue;
|
||||||
double myTimestamp;
|
uInt64 myTimestamp;
|
||||||
|
|
||||||
ConsoleTiming myConsoleTiming;
|
ConsoleTiming myConsoleTiming;
|
||||||
double myClockFreq;
|
double myClockFreq;
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "DelayQueueIteratorImpl.hxx"
|
#include "DelayQueueIteratorImpl.hxx"
|
||||||
#include "TIAConstants.hxx"
|
#include "TIAConstants.hxx"
|
||||||
#include "frame-manager/FrameManager.hxx"
|
#include "frame-manager/FrameManager.hxx"
|
||||||
|
#include "AudioQueue.hxx"
|
||||||
|
#include "DispatchResult.hxx"
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
#include "CartDebug.hxx"
|
#include "CartDebug.hxx"
|
||||||
|
@ -65,9 +67,8 @@ enum ResxCounter: uInt8 {
|
||||||
static constexpr uInt8 resxLateHblankThreshold = 73;
|
static constexpr uInt8 resxLateHblankThreshold = 73;
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
TIA::TIA(Console& console, Sound& sound, Settings& settings)
|
TIA::TIA(Console& console, Settings& settings)
|
||||||
: myConsole(console),
|
: myConsole(console),
|
||||||
mySound(sound),
|
|
||||||
mySettings(settings),
|
mySettings(settings),
|
||||||
myFrameManager(nullptr),
|
myFrameManager(nullptr),
|
||||||
myPlayfield(~CollisionMask::playfield & 0x7FFF),
|
myPlayfield(~CollisionMask::playfield & 0x7FFF),
|
||||||
|
@ -116,6 +117,12 @@ void TIA::setFrameManager(AbstractFrameManager *frameManager)
|
||||||
myFrameManager->setJitterFactor(myJitterFactor);
|
myFrameManager->setJitterFactor(myJitterFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void TIA::setAudioQueue(shared_ptr<AudioQueue> queue)
|
||||||
|
{
|
||||||
|
myAudio.setAudioQueue(queue);
|
||||||
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void TIA::clearFrameManager()
|
void TIA::clearFrameManager()
|
||||||
{
|
{
|
||||||
|
@ -138,7 +145,6 @@ void TIA::reset()
|
||||||
myCollisionMask = 0;
|
myCollisionMask = 0;
|
||||||
myLinesSinceChange = 0;
|
myLinesSinceChange = 0;
|
||||||
myCollisionUpdateRequired = false;
|
myCollisionUpdateRequired = false;
|
||||||
myAutoFrameEnabled = false;
|
|
||||||
myColorLossEnabled = myColorLossActive = false;
|
myColorLossEnabled = myColorLossActive = false;
|
||||||
myColorHBlank = 0;
|
myColorHBlank = 0;
|
||||||
myLastCycle = 0;
|
myLastCycle = 0;
|
||||||
|
@ -159,11 +165,12 @@ void TIA::reset()
|
||||||
myInput0.reset();
|
myInput0.reset();
|
||||||
myInput1.reset();
|
myInput1.reset();
|
||||||
|
|
||||||
|
myAudio.reset();
|
||||||
|
|
||||||
myTimestamp = 0;
|
myTimestamp = 0;
|
||||||
for (PaddleReader& paddleReader : myPaddleReaders)
|
for (PaddleReader& paddleReader : myPaddleReaders)
|
||||||
paddleReader.reset(myTimestamp);
|
paddleReader.reset(myTimestamp);
|
||||||
|
|
||||||
mySound.reset();
|
|
||||||
myDelayQueue.reset();
|
myDelayQueue.reset();
|
||||||
|
|
||||||
myCyclesAtFrameStart = 0;
|
myCyclesAtFrameStart = 0;
|
||||||
|
@ -174,6 +181,11 @@ void TIA::reset()
|
||||||
frameReset(); // Recalculate the size of the display
|
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
|
// Must be done last, after all other items have reset
|
||||||
enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors"));
|
enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors"));
|
||||||
setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
|
setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
|
||||||
|
@ -186,8 +198,9 @@ void TIA::reset()
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void TIA::frameReset()
|
void TIA::frameReset()
|
||||||
{
|
{
|
||||||
|
memset(myBackBuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||||
|
memset(myFrontBuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||||
memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||||
myAutoFrameEnabled = mySettings.getInt("framerate") <= 0;
|
|
||||||
enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss");
|
enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,8 +240,6 @@ bool TIA::save(Serializer& out) const
|
||||||
{
|
{
|
||||||
out.putString(name());
|
out.putString(name());
|
||||||
|
|
||||||
if(!mySound.save(out)) return false;
|
|
||||||
|
|
||||||
if(!myDelayQueue.save(out)) return false;
|
if(!myDelayQueue.save(out)) return false;
|
||||||
if(!myFrameManager->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(!myPlayer0.save(out)) return false;
|
||||||
if(!myPlayer1.save(out)) return false;
|
if(!myPlayer1.save(out)) return false;
|
||||||
if(!myBall.save(out)) return false;
|
if(!myBall.save(out)) return false;
|
||||||
|
if(!myAudio.save(out)) return false;
|
||||||
|
|
||||||
for (const PaddleReader& paddleReader : myPaddleReaders)
|
for (const PaddleReader& paddleReader : myPaddleReaders)
|
||||||
if(!paddleReader.save(out)) return false;
|
if(!paddleReader.save(out)) return false;
|
||||||
|
@ -275,11 +287,14 @@ bool TIA::save(Serializer& out) const
|
||||||
|
|
||||||
out.putLong(myTimestamp);
|
out.putLong(myTimestamp);
|
||||||
|
|
||||||
out.putBool(myAutoFrameEnabled);
|
|
||||||
|
|
||||||
out.putByteArray(myShadowRegisters, 64);
|
out.putByteArray(myShadowRegisters, 64);
|
||||||
|
|
||||||
out.putLong(myCyclesAtFrameStart);
|
out.putLong(myCyclesAtFrameStart);
|
||||||
|
|
||||||
|
out.putInt(myFrameBufferScanlines);
|
||||||
|
out.putInt(myFrontBufferScanlines);
|
||||||
|
out.putDouble(myFrameBufferFrameRate);
|
||||||
|
out.putDouble(myFrontBufferFrameRate);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
@ -298,8 +313,6 @@ bool TIA::load(Serializer& in)
|
||||||
if(in.getString() != name())
|
if(in.getString() != name())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!mySound.load(in)) return false;
|
|
||||||
|
|
||||||
if(!myDelayQueue.load(in)) return false;
|
if(!myDelayQueue.load(in)) return false;
|
||||||
if(!myFrameManager->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(!myPlayer0.load(in)) return false;
|
||||||
if(!myPlayer1.load(in)) return false;
|
if(!myPlayer1.load(in)) return false;
|
||||||
if(!myBall.load(in)) return false;
|
if(!myBall.load(in)) return false;
|
||||||
|
if(!myAudio.load(in)) return false;
|
||||||
|
|
||||||
for (PaddleReader& paddleReader : myPaddleReaders)
|
for (PaddleReader& paddleReader : myPaddleReaders)
|
||||||
if(!paddleReader.load(in)) return false;
|
if(!paddleReader.load(in)) return false;
|
||||||
|
@ -346,11 +360,14 @@ bool TIA::load(Serializer& in)
|
||||||
|
|
||||||
myTimestamp = in.getLong();
|
myTimestamp = in.getLong();
|
||||||
|
|
||||||
myAutoFrameEnabled = in.getBool();
|
|
||||||
|
|
||||||
in.getByteArray(myShadowRegisters, 64);
|
in.getByteArray(myShadowRegisters, 64);
|
||||||
|
|
||||||
myCyclesAtFrameStart = in.getLong();
|
myCyclesAtFrameStart = in.getLong();
|
||||||
|
|
||||||
|
myFrameBufferScanlines = in.getInt();
|
||||||
|
myFrontBufferScanlines = in.getInt();
|
||||||
|
myFrameBufferFrameRate = in.getDouble();
|
||||||
|
myFrontBufferFrameRate = in.getDouble();
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
@ -521,33 +538,35 @@ bool TIA::poke(uInt16 address, uInt8 value)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
// FIXME - rework this when we add the new sound core
|
|
||||||
case AUDV0:
|
case AUDV0:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel0().audv(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDV1:
|
case AUDV1:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel1().audv(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDF0:
|
case AUDF0:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel0().audf(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDF1:
|
case AUDF1:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel1().audf(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDC0:
|
case AUDC0:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel0().audc(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDC1:
|
case AUDC1:
|
||||||
mySound.set(address, value, mySystem->cycles());
|
myAudio.channel1().audc(value);
|
||||||
myShadowRegisters[address] = value;
|
myShadowRegisters[address] = value;
|
||||||
break;
|
break;
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
case HMOVE:
|
case HMOVE:
|
||||||
myDelayQueue.push(HMOVE, value, Delay::hmove);
|
myDelayQueue.push(HMOVE, value, Delay::hmove);
|
||||||
|
@ -777,7 +796,10 @@ bool TIA::saveDisplay(Serializer& out) const
|
||||||
{
|
{
|
||||||
try
|
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(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
@ -795,6 +817,9 @@ bool TIA::loadDisplay(Serializer& in)
|
||||||
{
|
{
|
||||||
// Reset frame buffer pointer and data
|
// Reset frame buffer pointer and data
|
||||||
in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
|
in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
|
||||||
|
in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||||
|
in.getByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||||
|
myNewFramePending = in.getBool();
|
||||||
}
|
}
|
||||||
catch(...)
|
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();
|
myCyclesAtFrameStart = mySystem->cycles();
|
||||||
|
|
||||||
if (myXAtRenderingStart > 0)
|
if (myXAtRenderingStart > 0)
|
||||||
memset(myFramebuffer, 0, myXAtRenderingStart);
|
memset(myBackBuffer, 0, myXAtRenderingStart);
|
||||||
|
|
||||||
// Blank out any extra lines not drawn this frame
|
// Blank out any extra lines not drawn this frame
|
||||||
const Int32 missingScanlines = myFrameManager->missingScanlines();
|
const Int32 missingScanlines = myFrameManager->missingScanlines();
|
||||||
if (missingScanlines > 0)
|
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'
|
memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||||
if(myAutoFrameEnabled)
|
|
||||||
myConsole.setFramerate(myFrameManager->frameRate());
|
myFrontBufferFrameRate = frameRate();
|
||||||
|
myFrontBufferScanlines = scanlinesLastFrame();
|
||||||
|
|
||||||
|
myNewFramePending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -1188,6 +1237,8 @@ void TIA::cycle(uInt32 colorClocks)
|
||||||
if (++myHctr >= 228)
|
if (++myHctr >= 228)
|
||||||
nextLine();
|
nextLine();
|
||||||
|
|
||||||
|
myAudio.tick();
|
||||||
|
|
||||||
myTimestamp++;
|
myTimestamp++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1261,7 +1312,7 @@ void TIA::applyRsync()
|
||||||
|
|
||||||
myHctrDelta = 225 - myHctr;
|
myHctrDelta = 225 - myHctr;
|
||||||
if (myFrameManager->isRendering())
|
if (myFrameManager->isRendering())
|
||||||
memset(myFramebuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
|
memset(myBackBuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
|
||||||
|
|
||||||
myHctr = 225;
|
myHctr = 225;
|
||||||
}
|
}
|
||||||
|
@ -1300,7 +1351,7 @@ void TIA::cloneLastLine()
|
||||||
|
|
||||||
if (!myFrameManager->isRendering() || y == 0) return;
|
if (!myFrameManager->isRendering() || y == 0) return;
|
||||||
|
|
||||||
uInt8* buffer = myFramebuffer;
|
uInt8* buffer = myBackBuffer;
|
||||||
|
|
||||||
memcpy(buffer + y * 160, buffer + (y-1) * 160, 160);
|
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()
|
void TIA::clearHmoveComb()
|
||||||
{
|
{
|
||||||
if (myFrameManager->isRendering() && myHstate == HState::blank)
|
if (myFrameManager->isRendering() && myHstate == HState::blank)
|
||||||
memset(myFramebuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
|
memset(myBackBuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "Console.hxx"
|
#include "Console.hxx"
|
||||||
#include "Sound.hxx"
|
|
||||||
#include "Settings.hxx"
|
#include "Settings.hxx"
|
||||||
#include "Device.hxx"
|
#include "Device.hxx"
|
||||||
#include "Serializer.hxx"
|
#include "Serializer.hxx"
|
||||||
|
@ -29,6 +28,7 @@
|
||||||
#include "DelayQueueIterator.hxx"
|
#include "DelayQueueIterator.hxx"
|
||||||
#include "frame-manager/AbstractFrameManager.hxx"
|
#include "frame-manager/AbstractFrameManager.hxx"
|
||||||
#include "FrameLayout.hxx"
|
#include "FrameLayout.hxx"
|
||||||
|
#include "Audio.hxx"
|
||||||
#include "Background.hxx"
|
#include "Background.hxx"
|
||||||
#include "Playfield.hxx"
|
#include "Playfield.hxx"
|
||||||
#include "Missile.hxx"
|
#include "Missile.hxx"
|
||||||
|
@ -40,6 +40,9 @@
|
||||||
#include "Control.hxx"
|
#include "Control.hxx"
|
||||||
#include "System.hxx"
|
#include "System.hxx"
|
||||||
|
|
||||||
|
class AudioQueue;
|
||||||
|
class DispatchResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class is a device that emulates the Television Interface Adaptor
|
This class is a device that emulates the Television Interface Adaptor
|
||||||
found in the Atari 2600 and 7800 consoles. The Television Interface
|
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
|
Create a new TIA for the specified console
|
||||||
|
|
||||||
@param console The console the TIA is associated with
|
@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
|
@param settings The settings object for this TIA device
|
||||||
*/
|
*/
|
||||||
TIA(Console& console, Sound& sound, Settings& settings);
|
TIA(Console& console, Settings& settings);
|
||||||
|
|
||||||
virtual ~TIA() = default;
|
virtual ~TIA() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Configure the frame manager.
|
Configure the frame manager.
|
||||||
*/
|
*/
|
||||||
void setFrameManager(AbstractFrameManager *frameManager);
|
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();
|
void clearFrameManager();
|
||||||
|
|
||||||
|
@ -192,7 +200,25 @@ class TIA : public Device
|
||||||
desired frame rate to update the TIA. Invoking this method will update
|
desired frame rate to update the TIA. Invoking this method will update
|
||||||
the graphics buffer and generate the corresponding audio samples.
|
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.
|
Returns a pointer to the internal frame buffer.
|
||||||
|
@ -222,13 +248,12 @@ class TIA : public Device
|
||||||
*/
|
*/
|
||||||
ConsoleTiming consoleTiming() const { return myConsole.timing(); }
|
ConsoleTiming consoleTiming() const { return myConsole.timing(); }
|
||||||
|
|
||||||
/**
|
float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; }
|
||||||
Enables/disables auto-frame calculation. If enabled, the TIA
|
|
||||||
re-adjusts the framerate at regular intervals.
|
|
||||||
|
|
||||||
@param enabled Whether to enable or disable all auto-frame calculation
|
/**
|
||||||
*/
|
The same, but for the frame in the frame buffer.
|
||||||
void enableAutoFrame(bool enabled) { myAutoFrameEnabled = enabled; }
|
*/
|
||||||
|
float frameBufferFrameRate() const { return myFrameBufferFrameRate; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Enables/disables color-loss for PAL modes only.
|
Enables/disables color-loss for PAL modes only.
|
||||||
|
@ -275,6 +300,11 @@ class TIA : public Device
|
||||||
*/
|
*/
|
||||||
uInt32 scanlinesLastFrame() const { return myFrameManager->scanlinesLastFrame(); }
|
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.
|
Answers the total system cycles from the start of the emulation.
|
||||||
*/
|
*/
|
||||||
|
@ -604,7 +634,6 @@ class TIA : public Device
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Console& myConsole;
|
Console& myConsole;
|
||||||
Sound& mySound;
|
|
||||||
Settings& mySettings;
|
Settings& mySettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -641,6 +670,8 @@ class TIA : public Device
|
||||||
Player myPlayer1;
|
Player myPlayer1;
|
||||||
Ball myBall;
|
Ball myBall;
|
||||||
|
|
||||||
|
Audio myAudio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The paddle readout circuits.
|
* The paddle readout circuits.
|
||||||
*/
|
*/
|
||||||
|
@ -655,6 +686,19 @@ class TIA : public Device
|
||||||
// Pointer to the internal color-index-based frame buffer
|
// Pointer to the internal color-index-based frame buffer
|
||||||
uInt8 myFramebuffer[160 * TIAConstants::frameBufferHeight];
|
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.
|
* Setting this to true injects random values into undefined reads.
|
||||||
*/
|
*/
|
||||||
|
@ -740,11 +784,9 @@ class TIA : public Device
|
||||||
uInt8 myColorHBlank;
|
uInt8 myColorHBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total number of color clocks since emulation started. This is a
|
* The total number of color clocks since emulation started.
|
||||||
* double a) to avoid overflows and b) as it will enter floating point
|
|
||||||
* expressions in the paddle readout simulation anyway.
|
|
||||||
*/
|
*/
|
||||||
double myTimestamp;
|
uInt64 myTimestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "shadow registers" track the last written register value for the
|
* The "shadow registers" track the last written register value for the
|
||||||
|
@ -752,11 +794,6 @@ class TIA : public Device
|
||||||
*/
|
*/
|
||||||
uInt8 myShadowRegisters[64];
|
uInt8 myShadowRegisters[64];
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatic framerate correction based on number of scanlines.
|
|
||||||
*/
|
|
||||||
bool myAutoFrameEnabled;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if color loss should be enabled or disabled. Color loss
|
* Indicates if color loss should be enabled or disabled. Color loss
|
||||||
* occurs on PAL-like systems when the previous frame contains an odd
|
* occurs on PAL-like systems when the previous frame contains an odd
|
||||||
|
|
|
@ -47,9 +47,7 @@ FrameManager::FrameManager()
|
||||||
myHeight(0),
|
myHeight(0),
|
||||||
myFixedHeight(0),
|
myFixedHeight(0),
|
||||||
myYStart(0),
|
myYStart(0),
|
||||||
myJitterEnabled(false),
|
myJitterEnabled(false)
|
||||||
myStableFrameLines(-1),
|
|
||||||
myStableFrameHeightCountdown(0)
|
|
||||||
{
|
{
|
||||||
onLayoutChange();
|
onLayoutChange();
|
||||||
}
|
}
|
||||||
|
@ -63,9 +61,6 @@ void FrameManager::onReset()
|
||||||
myVsyncLines = 0;
|
myVsyncLines = 0;
|
||||||
myY = 0;
|
myY = 0;
|
||||||
|
|
||||||
myStableFrameLines = -1;
|
|
||||||
myStableFrameHeightCountdown = 0;
|
|
||||||
|
|
||||||
myJitterEmulation.reset();
|
myJitterEmulation.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,9 +225,6 @@ bool FrameManager::onSave(Serializer& out) const
|
||||||
|
|
||||||
out.putBool(myJitterEnabled);
|
out.putBool(myJitterEnabled);
|
||||||
|
|
||||||
out.putInt(myStableFrameLines);
|
|
||||||
out.putInt(myStableFrameHeightCountdown);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,8 +249,5 @@ bool FrameManager::onLoad(Serializer& in)
|
||||||
|
|
||||||
myJitterEnabled = in.getBool();
|
myJitterEnabled = in.getBool();
|
||||||
|
|
||||||
myStableFrameLines = in.getInt();
|
|
||||||
myStableFrameHeightCountdown = in.getInt();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,9 +100,6 @@ class FrameManager: public AbstractFrameManager {
|
||||||
|
|
||||||
bool myJitterEnabled;
|
bool myJitterEnabled;
|
||||||
|
|
||||||
Int32 myStableFrameLines;
|
|
||||||
uInt8 myStableFrameHeightCountdown;
|
|
||||||
|
|
||||||
JitterEmulation myJitterEmulation;
|
JitterEmulation myJitterEmulation;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -9,7 +9,9 @@ MODULE_OBJS := \
|
||||||
src/emucore/tia/Ball.o \
|
src/emucore/tia/Ball.o \
|
||||||
src/emucore/tia/Background.o \
|
src/emucore/tia/Background.o \
|
||||||
src/emucore/tia/LatchedInput.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 += \
|
MODULE_DIRS += \
|
||||||
src/emucore/tia
|
src/emucore/tia
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "Settings.hxx"
|
#include "Settings.hxx"
|
||||||
#include "Sound.hxx"
|
#include "Sound.hxx"
|
||||||
#include "Widget.hxx"
|
#include "Widget.hxx"
|
||||||
|
#include "AudioSettings.hxx"
|
||||||
|
|
||||||
#include "AudioDialog.hxx"
|
#include "AudioDialog.hxx"
|
||||||
|
|
||||||
|
@ -44,14 +45,14 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
fontWidth = font.getMaxCharWidth(),
|
fontWidth = font.getMaxCharWidth(),
|
||||||
fontHeight = font.getFontHeight();
|
fontHeight = font.getFontHeight();
|
||||||
int xpos, ypos;
|
int xpos, ypos;
|
||||||
int lwidth = font.getStringWidth("Sample Size (*) "),
|
int lwidth = font.getStringWidth("Resampling quality "),
|
||||||
pwidth = font.getStringWidth("512 bytes");
|
pwidth = font.getStringWidth("512 bytes");
|
||||||
WidgetArray wid;
|
WidgetArray wid;
|
||||||
VariantList items;
|
VariantList items;
|
||||||
|
|
||||||
// Set real dimensions
|
// Set real dimensions
|
||||||
_w = 35 * fontWidth + HBORDER * 2;
|
_w = 45 * fontWidth + HBORDER * 2;
|
||||||
_h = 7 * (lineHeight + 4) + VBORDER + _th;
|
_h = 11 * (lineHeight + 4) + VBORDER + _th;
|
||||||
|
|
||||||
xpos = HBORDER; ypos = VBORDER + _th;
|
xpos = HBORDER; ypos = VBORDER + _th;
|
||||||
|
|
||||||
|
@ -64,43 +65,80 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
xpos += INDENT;
|
xpos += INDENT;
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
myVolumeSlider = new SliderWidget(this, font, xpos, ypos, 11 * fontWidth + 5, lineHeight,
|
myVolumeSlider = new SliderWidget(this, font, xpos, ypos,
|
||||||
"Volume ", lwidth, 0, 4 * fontWidth, "%");
|
"Volume ", 0, 0, 4 * fontWidth, "%");
|
||||||
myVolumeSlider->setMinValue(1); myVolumeSlider->setMaxValue(100);
|
myVolumeSlider->setMinValue(1); myVolumeSlider->setMaxValue(100);
|
||||||
wid.push_back(myVolumeSlider);
|
wid.push_back(myVolumeSlider);
|
||||||
ypos += lineHeight + 4;
|
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
|
// Fragment size
|
||||||
VarList::push_back(items, "128 bytes", "128");
|
items.clear();
|
||||||
VarList::push_back(items, "256 bytes", "256");
|
VarList::push_back(items, "128 bytes", 128);
|
||||||
VarList::push_back(items, "512 bytes", "512");
|
VarList::push_back(items, "256 bytes", 256);
|
||||||
VarList::push_back(items, "1 KB", "1024");
|
VarList::push_back(items, "512 bytes", 512);
|
||||||
VarList::push_back(items, "2 KB", "2048");
|
VarList::push_back(items, "1 KB", 1024);
|
||||||
VarList::push_back(items, "4 KB", "4096");
|
VarList::push_back(items, "2 KB", 2048);
|
||||||
|
VarList::push_back(items, "4 KB", 4096);
|
||||||
myFragsizePopup = new PopUpWidget(this, font, xpos, ypos,
|
myFragsizePopup = new PopUpWidget(this, font, xpos, ypos,
|
||||||
pwidth, lineHeight,
|
pwidth, lineHeight,
|
||||||
items, "Sample size (*) ", lwidth);
|
items, "Fragment size (*) ", lwidth);
|
||||||
wid.push_back(myFragsizePopup);
|
wid.push_back(myFragsizePopup);
|
||||||
ypos += lineHeight + 4;
|
ypos += lineHeight + 4;
|
||||||
|
|
||||||
// Output frequency
|
// Output frequency
|
||||||
items.clear();
|
items.clear();
|
||||||
VarList::push_back(items, "11025 Hz", "11025");
|
VarList::push_back(items, "44100 Hz", 44100);
|
||||||
VarList::push_back(items, "22050 Hz", "22050");
|
VarList::push_back(items, "48000 Hz", 48000);
|
||||||
VarList::push_back(items, "31400 Hz", "31400");
|
VarList::push_back(items, "96000 Hz", 96000);
|
||||||
VarList::push_back(items, "44100 Hz", "44100");
|
|
||||||
VarList::push_back(items, "48000 Hz", "48000");
|
|
||||||
myFreqPopup = new PopUpWidget(this, font, xpos, ypos,
|
myFreqPopup = new PopUpWidget(this, font, xpos, ypos,
|
||||||
pwidth, lineHeight,
|
pwidth, lineHeight,
|
||||||
items, "Frequency (*) ", lwidth);
|
items, "Sample rate (*) ", lwidth);
|
||||||
wid.push_back(myFreqPopup);
|
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
|
// Add message concerning usage
|
||||||
ypos = _h - fontHeight * 2 - 24;
|
ypos = _h - fontHeight * 2 - 24;
|
||||||
const GUI::Font& infofont = instance().frameBuffer().infoFont();
|
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,
|
font.getStringWidth("(*) Requires application restart"), fontHeight,
|
||||||
"(*) Requires application restart", TextAlign::Left);
|
"(*) Requires application restart", TextAlign::Left);*/
|
||||||
|
|
||||||
// Add Defaults, OK and Cancel buttons
|
// Add Defaults, OK and Cancel buttons
|
||||||
addDefaultsOKCancelBGroup(wid, font);
|
addDefaultsOKCancelBGroup(wid, font);
|
||||||
|
@ -111,41 +149,69 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void AudioDialog::loadConfig()
|
void AudioDialog::loadConfig()
|
||||||
{
|
{
|
||||||
|
AudioSettings& audioSettings = instance().audioSettings();
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
myVolumeSlider->setValue(instance().settings().getInt("volume"));
|
myVolumeSlider->setValue(audioSettings.volume());
|
||||||
|
|
||||||
// Fragsize
|
|
||||||
myFragsizePopup->setSelected(instance().settings().getString("fragsize"), "512");
|
|
||||||
|
|
||||||
// Output frequency
|
|
||||||
myFreqPopup->setSelected(instance().settings().getString("freq"), "31400");
|
|
||||||
|
|
||||||
// Enable sound
|
// Enable sound
|
||||||
bool b = instance().settings().getBool("sound");
|
mySoundEnableCheckbox->setState(audioSettings.enabled());
|
||||||
mySoundEnableCheckbox->setState(b);
|
|
||||||
|
// 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
|
// 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()
|
void AudioDialog::saveConfig()
|
||||||
{
|
{
|
||||||
Settings& settings = instance().settings();
|
AudioSettings audioSettings = instance().audioSettings();
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
settings.setValue("volume", myVolumeSlider->getValue());
|
audioSettings.setVolume(myVolumeSlider->getValue());
|
||||||
instance().sound().setVolume(myVolumeSlider->getValue());
|
instance().sound().setVolume(myVolumeSlider->getValue());
|
||||||
|
|
||||||
// Fragsize
|
audioSettings.setEnabled(mySoundEnableCheckbox->getState());
|
||||||
settings.setValue("fragsize", myFragsizePopup->getSelectedTag().toString());
|
|
||||||
|
|
||||||
// Output frequency
|
|
||||||
settings.setValue("freq", myFreqPopup->getSelectedTag().toString());
|
|
||||||
|
|
||||||
// Enable/disable sound (requires a restart to take effect)
|
|
||||||
instance().sound().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
|
// Only force a re-initialization when necessary, since it can
|
||||||
// be a time-consuming operation
|
// be a time-consuming operation
|
||||||
if(instance().hasConsole())
|
if(instance().hasConsole())
|
||||||
|
@ -172,10 +238,33 @@ void AudioDialog::setDefaults()
|
||||||
void AudioDialog::handleSoundEnableChange(bool active)
|
void AudioDialog::handleSoundEnableChange(bool active)
|
||||||
{
|
{
|
||||||
myVolumeSlider->setEnabled(active);
|
myVolumeSlider->setEnabled(active);
|
||||||
myFragsizePopup->setEnabled(active);
|
myModePopup->setEnabled(active);
|
||||||
myFreqPopup->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,
|
void AudioDialog::handleCommand(CommandSender* sender, int cmd,
|
||||||
int data, int id)
|
int data, int id)
|
||||||
|
@ -195,6 +284,10 @@ void AudioDialog::handleCommand(CommandSender* sender, int cmd,
|
||||||
handleSoundEnableChange(data == 1);
|
handleSoundEnableChange(data == 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case kModeChanged:
|
||||||
|
handleModeChange(true);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Dialog::handleCommand(sender, cmd, data, 0);
|
Dialog::handleCommand(sender, cmd, data, 0);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -26,6 +26,7 @@ class SliderWidget;
|
||||||
class StaticTextWidget;
|
class StaticTextWidget;
|
||||||
class CheckboxWidget;
|
class CheckboxWidget;
|
||||||
class OSystem;
|
class OSystem;
|
||||||
|
class AudioSettings;
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
|
|
||||||
|
@ -41,17 +42,27 @@ class AudioDialog : public Dialog
|
||||||
void setDefaults() override;
|
void setDefaults() override;
|
||||||
|
|
||||||
void handleSoundEnableChange(bool active);
|
void handleSoundEnableChange(bool active);
|
||||||
|
void handleModeChange(bool active);
|
||||||
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
|
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum {
|
enum {
|
||||||
kSoundEnableChanged = 'ADse'
|
kSoundEnableChanged = 'ADse',
|
||||||
|
kModeChanged = 'ADmc'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CheckboxWidget* mySoundEnableCheckbox;
|
||||||
SliderWidget* myVolumeSlider;
|
SliderWidget* myVolumeSlider;
|
||||||
|
PopUpWidget* myModePopup;
|
||||||
PopUpWidget* myFragsizePopup;
|
PopUpWidget* myFragsizePopup;
|
||||||
PopUpWidget* myFreqPopup;
|
PopUpWidget* myFreqPopup;
|
||||||
CheckboxWidget* mySoundEnableCheckbox;
|
PopUpWidget* myResamplingPopup;
|
||||||
|
SliderWidget* myHeadroomSlider;
|
||||||
|
SliderWidget* myBufferSizeSlider;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void updatePresetSettings(AudioSettings&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
|
|
|
@ -795,7 +795,7 @@ bool Dialog::getResizableBounds(uInt32& w, uInt32& h) const
|
||||||
|
|
||||||
if(instance().hasConsole())
|
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");
|
uInt32 aspect = instance().settings().getInt(ntsc ?"tia.aspectn" : "tia.aspectp");
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "Control.hxx"
|
#include "Control.hxx"
|
||||||
#include "Dialog.hxx"
|
#include "Dialog.hxx"
|
||||||
|
@ -32,6 +34,45 @@
|
||||||
#include "TIASurface.hxx"
|
#include "TIASurface.hxx"
|
||||||
#include "VideoDialog.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,
|
VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
const GUI::Font& font, int max_w, int max_h)
|
const GUI::Font& font, int max_w, int max_h)
|
||||||
|
@ -121,13 +162,13 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
wid.push_back(myPAspectRatio);
|
wid.push_back(myPAspectRatio);
|
||||||
ypos += lineHeight + VGAP;
|
ypos += lineHeight + VGAP;
|
||||||
|
|
||||||
// Framerate
|
// Speed
|
||||||
myFrameRate =
|
mySpeed =
|
||||||
new SliderWidget(myTab, font, xpos, ypos-1, swidth, lineHeight,
|
new SliderWidget(myTab, font, xpos, ypos-1, swidth, lineHeight,
|
||||||
"Frame rate ", lwidth, kFrameRateChanged, fontWidth * 6, "fps");
|
"Speed ", lwidth, kSpeedupChanged, fontWidth * 8, "");
|
||||||
myFrameRate->setMinValue(0); myFrameRate->setMaxValue(900);
|
mySpeed->setMinValue(MIN_SPEED); mySpeed->setMaxValue(MAX_SPEED);
|
||||||
myFrameRate->setStepValue(10);
|
mySpeed->setStepValue(SPEED_STEP);
|
||||||
wid.push_back(myFrameRate);
|
wid.push_back(mySpeed);
|
||||||
ypos += lineHeight + VGAP;
|
ypos += lineHeight + VGAP;
|
||||||
|
|
||||||
// Use sync to vblank
|
// Use sync to vblank
|
||||||
|
@ -141,7 +182,7 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
||||||
"(*) Requires application restart");
|
"(*) Requires application restart");
|
||||||
|
|
||||||
// Move over to the next column
|
// Move over to the next column
|
||||||
xpos += myFrameRate->getWidth() + 16;
|
xpos += mySpeed->getWidth() + 16;
|
||||||
ypos = VBORDER;
|
ypos = VBORDER;
|
||||||
|
|
||||||
// Fullscreen
|
// Fullscreen
|
||||||
|
@ -327,12 +368,10 @@ void VideoDialog::loadConfig()
|
||||||
myNAspectRatio->setValue(instance().settings().getInt("tia.aspectn"));
|
myNAspectRatio->setValue(instance().settings().getInt("tia.aspectn"));
|
||||||
myPAspectRatio->setValue(instance().settings().getInt("tia.aspectp"));
|
myPAspectRatio->setValue(instance().settings().getInt("tia.aspectp"));
|
||||||
|
|
||||||
// Framerate (0 or -1 means automatic framerate calculation)
|
// Emulation speed
|
||||||
int rate = instance().settings().getInt("framerate");
|
int speed = mapSpeed(instance().settings().getFloat("speed"));
|
||||||
myFrameRate->setValue(rate < 0 ? 0 : rate);
|
mySpeed->setValue(speed);
|
||||||
myFrameRate->setValueLabel(rate <= 0 ? "Auto" :
|
mySpeed->setValueLabel(formatSpeed(speed));
|
||||||
instance().settings().getString("framerate"));
|
|
||||||
myFrameRate->setValueUnit(rate <= 0 ? "" : "fps");
|
|
||||||
|
|
||||||
// Fullscreen
|
// Fullscreen
|
||||||
myFullscreen->setState(instance().settings().getBool("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.aspectn", myNAspectRatio->getValueLabel());
|
||||||
instance().settings().setValue("tia.aspectp", myPAspectRatio->getValueLabel());
|
instance().settings().setValue("tia.aspectp", myPAspectRatio->getValueLabel());
|
||||||
|
|
||||||
// Framerate
|
// Speed
|
||||||
int f = myFrameRate->getValue();
|
int speedup = mySpeed->getValue();
|
||||||
instance().settings().setValue("framerate", f);
|
instance().settings().setValue("speed", unmapSpeed(speedup));
|
||||||
if(instance().hasConsole())
|
if (instance().hasConsole()) instance().console().initializeAudio();
|
||||||
{
|
|
||||||
// Make sure auto-frame calculation is only enabled when necessary
|
|
||||||
instance().console().tia().enableAutoFrame(f <= 0);
|
|
||||||
instance().console().setFramerate(float(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fullscreen
|
// Fullscreen
|
||||||
instance().settings().setValue("fullscreen", myFullscreen->getState());
|
instance().settings().setValue("fullscreen", myFullscreen->getState());
|
||||||
|
@ -486,7 +520,7 @@ void VideoDialog::setDefaults()
|
||||||
myTIAInterpolate->setState(false);
|
myTIAInterpolate->setState(false);
|
||||||
myNAspectRatio->setValue(91);
|
myNAspectRatio->setValue(91);
|
||||||
myPAspectRatio->setValue(109);
|
myPAspectRatio->setValue(109);
|
||||||
myFrameRate->setValue(0);
|
mySpeed->setValue(0);
|
||||||
|
|
||||||
myFullscreen->setState(false);
|
myFullscreen->setState(false);
|
||||||
//myFullScreenMode->setSelectedIndex(0);
|
//myFullScreenMode->setSelectedIndex(0);
|
||||||
|
@ -585,10 +619,8 @@ void VideoDialog::handleCommand(CommandSender* sender, int cmd,
|
||||||
setDefaults();
|
setDefaults();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kFrameRateChanged:
|
case kSpeedupChanged:
|
||||||
if(myFrameRate->getValue() == 0)
|
mySpeed->setValueLabel(formatSpeed(mySpeed->getValue()));
|
||||||
myFrameRate->setValueLabel("Auto");
|
|
||||||
myFrameRate->setValueUnit(myFrameRate->getValue() == 0 ? "" : "fps");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kTVModeChanged:
|
case kTVModeChanged:
|
||||||
|
|
|
@ -58,7 +58,7 @@ class VideoDialog : public Dialog
|
||||||
CheckboxWidget* myTIAInterpolate;
|
CheckboxWidget* myTIAInterpolate;
|
||||||
SliderWidget* myNAspectRatio;
|
SliderWidget* myNAspectRatio;
|
||||||
SliderWidget* myPAspectRatio;
|
SliderWidget* myPAspectRatio;
|
||||||
SliderWidget* myFrameRate;
|
SliderWidget* mySpeed;
|
||||||
|
|
||||||
CheckboxWidget* myFullscreen;
|
CheckboxWidget* myFullscreen;
|
||||||
//PopUpWidget* myFullScreenMode;
|
//PopUpWidget* myFullScreenMode;
|
||||||
|
@ -99,7 +99,7 @@ class VideoDialog : public Dialog
|
||||||
ButtonWidget* myCloneCustom;
|
ButtonWidget* myCloneCustom;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
kFrameRateChanged = 'VDfr',
|
kSpeedupChanged = 'VDSp',
|
||||||
|
|
||||||
kTVModeChanged = 'VDtv',
|
kTVModeChanged = 'VDtv',
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,6 @@
|
||||||
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20F9FF08C603EC00A73076 /* TogglePixelWidget.hxx */; };
|
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20F9FF08C603EC00A73076 /* TogglePixelWidget.hxx */; };
|
||||||
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20FA0108C603EC00A73076 /* ToggleWidget.hxx */; };
|
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20FA0108C603EC00A73076 /* ToggleWidget.hxx */; };
|
||||||
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D6CC10408C811A600B8F642 /* TiaZoomWidget.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 */; };
|
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D2331900900B5EF00613B1F /* AudioWidget.hxx */; };
|
||||||
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF5F096E269100A518FE /* EventMappingWidget.hxx */; };
|
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF5F096E269100A518FE /* EventMappingWidget.hxx */; };
|
||||||
2D91746A09BA90380026E9FF /* InputDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF61096E269100A518FE /* InputDialog.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 */; };
|
2D91750309BA90380026E9FF /* TogglePixelWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F9FE08C603EC00A73076 /* TogglePixelWidget.cxx */; };
|
||||||
2D91750409BA90380026E9FF /* ToggleWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20FA0008C603EC00A73076 /* ToggleWidget.cxx */; };
|
2D91750409BA90380026E9FF /* ToggleWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20FA0008C603EC00A73076 /* ToggleWidget.cxx */; };
|
||||||
2D91750609BA90380026E9FF /* TiaZoomWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D6CC10308C811A600B8F642 /* TiaZoomWidget.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 */; };
|
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D23318F0900B5EF00613B1F /* AudioWidget.cxx */; };
|
||||||
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF5E096E269100A518FE /* EventMappingWidget.cxx */; };
|
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF5E096E269100A518FE /* EventMappingWidget.cxx */; };
|
||||||
2D91750C09BA90380026E9FF /* InputDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF60096E269100A518FE /* InputDialog.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 */; };
|
DCC527D610B9DA19005E1287 /* System.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC527CE10B9DA19005E1287 /* System.cxx */; };
|
||||||
DCC527D710B9DA19005E1287 /* System.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527CF10B9DA19005E1287 /* System.hxx */; };
|
DCC527D710B9DA19005E1287 /* System.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527CF10B9DA19005E1287 /* System.hxx */; };
|
||||||
DCC527DB10B9DA6A005E1287 /* bspf.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527D810B9DA6A005E1287 /* bspf.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 */; };
|
DCCA26B31FA64D5E000EE4D8 /* AbstractFrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */; };
|
||||||
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */; };
|
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */; };
|
||||||
DCCF47DE14B60DEE00814FAB /* ControllerWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCF47DB14B60DEE00814FAB /* ControllerWidget.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 */; };
|
DCDE17FB17724E5D00EB1AC6 /* ConfigPathDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */; };
|
||||||
DCDE17FC17724E5D00EB1AC6 /* SnapshotDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */; };
|
DCDE17FC17724E5D00EB1AC6 /* SnapshotDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */; };
|
||||||
DCDE17FD17724E5D00EB1AC6 /* SnapshotDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */; };
|
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 */; };
|
DCE395DB16CB0B2B008DB1E5 /* FSNodePOSIX.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */; };
|
||||||
DCE395EF16CB0B5F008DB1E5 /* FSNodeFactory.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */; };
|
DCE395EF16CB0B5F008DB1E5 /* FSNodeFactory.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */; };
|
||||||
DCE395F016CB0B5F008DB1E5 /* FSNodeZIP.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */; };
|
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 */; };
|
DCF3A6FD1DFC75E3008A8AF3 /* Playfield.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */; };
|
||||||
DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */; };
|
DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */; };
|
||||||
DCF3A6FF1DFC75E3008A8AF3 /* TIA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */; };
|
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 */; };
|
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467B40F93993B00B25D7A /* SoundNull.hxx */; };
|
||||||
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467BC0F9399F500B25D7A /* Version.hxx */; };
|
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467BC0F9399F500B25D7A /* Version.hxx */; };
|
||||||
DCF467C20F939A1400B25D7A /* CartEF.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF467BE0F939A1400B25D7A /* CartEF.cxx */; };
|
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 */; };
|
DCF7B0DF10A762FC007A2870 /* CartFA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF7B0DB10A762FC007A2870 /* CartFA.cxx */; };
|
||||||
DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF7B0DC10A762FC007A2870 /* CartFA.hxx */; };
|
DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF7B0DC10A762FC007A2870 /* CartFA.hxx */; };
|
||||||
DCFB9FAC1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.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 */; };
|
DCFF14CD18B0260300A20364 /* EventHandlerSDL2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */; };
|
||||||
DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; };
|
DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; };
|
||||||
DCFFE59D12100E1400DFA000 /* ComboDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */; };
|
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 */; };
|
E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E091F93E915003DDD52 /* JitterEmulation.cxx */; };
|
||||||
E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; };
|
E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; };
|
||||||
E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; };
|
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 */; };
|
E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; };
|
||||||
E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXBuildRule section */
|
/* Begin PBXBuildRule section */
|
||||||
|
@ -874,8 +890,6 @@
|
||||||
2DE2DF8D0627AE34006BEC99 /* Sound.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Sound.hxx; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
@ -1578,6 +1610,9 @@
|
||||||
2D6050C5089876F300C6DE89 /* common */ = {
|
2D6050C5089876F300C6DE89 /* common */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
DCC6A4AD20A2620D00863C59 /* audio */,
|
||||||
|
E09F413A201E901D004A3391 /* AudioQueue.cxx */,
|
||||||
|
E09F4139201E901C004A3391 /* AudioQueue.hxx */,
|
||||||
DC79F81017A88D9E00288B91 /* Base.cxx */,
|
DC79F81017A88D9E00288B91 /* Base.cxx */,
|
||||||
DC79F81117A88D9E00288B91 /* Base.hxx */,
|
DC79F81117A88D9E00288B91 /* Base.hxx */,
|
||||||
DCC527D810B9DA6A005E1287 /* bspf.hxx */,
|
DCC527D810B9DA6A005E1287 /* bspf.hxx */,
|
||||||
|
@ -1756,8 +1791,14 @@
|
||||||
2DE2DF3B0627AE07006BEC99 /* Control.hxx */,
|
2DE2DF3B0627AE07006BEC99 /* Control.hxx */,
|
||||||
DC932D3F0F278A5200FEFEFC /* DefProps.hxx */,
|
DC932D3F0F278A5200FEFEFC /* DefProps.hxx */,
|
||||||
DCC527C910B9DA19005E1287 /* Device.hxx */,
|
DCC527C910B9DA19005E1287 /* Device.hxx */,
|
||||||
|
DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */,
|
||||||
|
DCDFF08020B781B0001227C0 /* DispatchResult.hxx */,
|
||||||
2DE2DF3E0627AE07006BEC99 /* Driving.cxx */,
|
2DE2DF3E0627AE07006BEC99 /* Driving.cxx */,
|
||||||
2DE2DF3F0627AE07006BEC99 /* Driving.hxx */,
|
2DE2DF3F0627AE07006BEC99 /* Driving.hxx */,
|
||||||
|
E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */,
|
||||||
|
E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */,
|
||||||
|
DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */,
|
||||||
|
DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */,
|
||||||
2DE2DF410627AE07006BEC99 /* Event.hxx */,
|
2DE2DF410627AE07006BEC99 /* Event.hxx */,
|
||||||
2D733D6E062895B2006265D9 /* EventHandler.cxx */,
|
2D733D6E062895B2006265D9 /* EventHandler.cxx */,
|
||||||
2D733D6F062895B2006265D9 /* EventHandler.hxx */,
|
2D733D6F062895B2006265D9 /* EventHandler.hxx */,
|
||||||
|
@ -1815,11 +1856,8 @@
|
||||||
DCD2839612E39F1200A808DC /* Thumbulator.cxx */,
|
DCD2839612E39F1200A808DC /* Thumbulator.cxx */,
|
||||||
DCD2839712E39F1200A808DC /* Thumbulator.hxx */,
|
DCD2839712E39F1200A808DC /* Thumbulator.hxx */,
|
||||||
DCE903E31DF5DCD10080A7F3 /* tia */,
|
DCE903E31DF5DCD10080A7F3 /* tia */,
|
||||||
2DE7242D08CE910900C889A8 /* TIASnd.cxx */,
|
|
||||||
2DE7242E08CE910900C889A8 /* TIASnd.hxx */,
|
|
||||||
DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */,
|
DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */,
|
||||||
DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */,
|
DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */,
|
||||||
DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */,
|
|
||||||
DC1B2EC21E50036100F62837 /* TrakBall.hxx */,
|
DC1B2EC21E50036100F62837 /* TrakBall.hxx */,
|
||||||
);
|
);
|
||||||
path = emucore;
|
path = emucore;
|
||||||
|
@ -2020,6 +2058,20 @@
|
||||||
path = tv_filters;
|
path = tv_filters;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
DCCC0C9109C3541E0088BFF1 /* cheat */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2070,6 +2122,10 @@
|
||||||
DCE903E31DF5DCD10080A7F3 /* tia */ = {
|
DCE903E31DF5DCD10080A7F3 /* tia */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E09F413E201E904F004A3391 /* Audio.cxx */,
|
||||||
|
E09F413D201E904F004A3391 /* Audio.hxx */,
|
||||||
|
E09F413F201E904F004A3391 /* AudioChannel.cxx */,
|
||||||
|
E09F4140201E904F004A3391 /* AudioChannel.hxx */,
|
||||||
DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */,
|
DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */,
|
||||||
DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */,
|
DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */,
|
||||||
DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */,
|
DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */,
|
||||||
|
@ -2124,11 +2180,13 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
2D9173CB09BA90380026E9FF /* SDLMain.h in Headers */,
|
2D9173CB09BA90380026E9FF /* SDLMain.h in Headers */,
|
||||||
|
E09F4141201E9050004A3391 /* Audio.hxx in Headers */,
|
||||||
2D9173CC09BA90380026E9FF /* Booster.hxx in Headers */,
|
2D9173CC09BA90380026E9FF /* Booster.hxx in Headers */,
|
||||||
2D9173CD09BA90380026E9FF /* Cart.hxx in Headers */,
|
2D9173CD09BA90380026E9FF /* Cart.hxx in Headers */,
|
||||||
2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */,
|
2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */,
|
||||||
2D9173CF09BA90380026E9FF /* Cart3F.hxx in Headers */,
|
2D9173CF09BA90380026E9FF /* Cart3F.hxx in Headers */,
|
||||||
DC3EE86D1E2C0E6D00905161 /* zlib.h in Headers */,
|
DC3EE86D1E2C0E6D00905161 /* zlib.h in Headers */,
|
||||||
|
E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */,
|
||||||
DC3EE86A1E2C0E6D00905161 /* trees.h in Headers */,
|
DC3EE86A1E2C0E6D00905161 /* trees.h in Headers */,
|
||||||
2D9173D009BA90380026E9FF /* Cart4K.hxx in Headers */,
|
2D9173D009BA90380026E9FF /* Cart4K.hxx in Headers */,
|
||||||
2D9173D109BA90380026E9FF /* CartAR.hxx in Headers */,
|
2D9173D109BA90380026E9FF /* CartAR.hxx in Headers */,
|
||||||
|
@ -2171,6 +2229,7 @@
|
||||||
2D9173EE09BA90380026E9FF /* Serializer.hxx in Headers */,
|
2D9173EE09BA90380026E9FF /* Serializer.hxx in Headers */,
|
||||||
2D9173EF09BA90380026E9FF /* Sound.hxx in Headers */,
|
2D9173EF09BA90380026E9FF /* Sound.hxx in Headers */,
|
||||||
2D9173F009BA90380026E9FF /* Switches.hxx in Headers */,
|
2D9173F009BA90380026E9FF /* Switches.hxx in Headers */,
|
||||||
|
E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */,
|
||||||
2D9173F909BA90380026E9FF /* EventHandler.hxx in Headers */,
|
2D9173F909BA90380026E9FF /* EventHandler.hxx in Headers */,
|
||||||
2D9173FA09BA90380026E9FF /* FrameBuffer.hxx in Headers */,
|
2D9173FA09BA90380026E9FF /* FrameBuffer.hxx in Headers */,
|
||||||
2D9173FB09BA90380026E9FF /* Settings.hxx in Headers */,
|
2D9173FB09BA90380026E9FF /* Settings.hxx in Headers */,
|
||||||
|
@ -2186,6 +2245,7 @@
|
||||||
2D91740309BA90380026E9FF /* Command.hxx in Headers */,
|
2D91740309BA90380026E9FF /* Command.hxx in Headers */,
|
||||||
DC3EE85B1E2C0E6D00905161 /* deflate.h in Headers */,
|
DC3EE85B1E2C0E6D00905161 /* deflate.h in Headers */,
|
||||||
2D91740409BA90380026E9FF /* Dialog.hxx in Headers */,
|
2D91740409BA90380026E9FF /* Dialog.hxx in Headers */,
|
||||||
|
E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */,
|
||||||
2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */,
|
2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */,
|
||||||
DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */,
|
DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */,
|
||||||
2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */,
|
2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */,
|
||||||
|
@ -2223,6 +2283,7 @@
|
||||||
DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */,
|
DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */,
|
||||||
2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */,
|
2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */,
|
||||||
DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */,
|
DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */,
|
||||||
|
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */,
|
||||||
2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */,
|
2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */,
|
||||||
2D91742909BA90380026E9FF /* TIADebug.hxx in Headers */,
|
2D91742909BA90380026E9FF /* TIADebug.hxx in Headers */,
|
||||||
2D91742A09BA90380026E9FF /* YaccParser.hxx in Headers */,
|
2D91742A09BA90380026E9FF /* YaccParser.hxx in Headers */,
|
||||||
|
@ -2246,6 +2307,7 @@
|
||||||
2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */,
|
2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */,
|
||||||
2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */,
|
2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */,
|
||||||
2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */,
|
2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */,
|
||||||
|
DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */,
|
||||||
DCF3A6EC1DFC75E3008A8AF3 /* DelayQueue.hxx in Headers */,
|
DCF3A6EC1DFC75E3008A8AF3 /* DelayQueue.hxx in Headers */,
|
||||||
DCA078341F8C1B04008EFEE5 /* LinkedObjectPool.hxx in Headers */,
|
DCA078341F8C1B04008EFEE5 /* LinkedObjectPool.hxx in Headers */,
|
||||||
2D91745809BA90380026E9FF /* DebuggerDialog.hxx in Headers */,
|
2D91745809BA90380026E9FF /* DebuggerDialog.hxx in Headers */,
|
||||||
|
@ -2267,7 +2329,6 @@
|
||||||
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */,
|
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */,
|
||||||
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */,
|
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */,
|
||||||
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */,
|
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */,
|
||||||
2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */,
|
|
||||||
DC1BC6672066B4390076F74A /* PKeyboardHandler.hxx in Headers */,
|
DC1BC6672066B4390076F74A /* PKeyboardHandler.hxx in Headers */,
|
||||||
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */,
|
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */,
|
||||||
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */,
|
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */,
|
||||||
|
@ -2285,6 +2346,7 @@
|
||||||
DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */,
|
DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */,
|
||||||
DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */,
|
DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */,
|
||||||
DCE3BBFA0C95CEDC00A671DF /* RomInfoWidget.hxx in Headers */,
|
DCE3BBFA0C95CEDC00A671DF /* RomInfoWidget.hxx in Headers */,
|
||||||
|
DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */,
|
||||||
DC6DC91F205DB879004A5FC3 /* PhysicalJoystick.hxx in Headers */,
|
DC6DC91F205DB879004A5FC3 /* PhysicalJoystick.hxx in Headers */,
|
||||||
DC0984860D3985160073C852 /* CartSB.hxx in Headers */,
|
DC0984860D3985160073C852 /* CartSB.hxx in Headers */,
|
||||||
DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */,
|
DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */,
|
||||||
|
@ -2298,6 +2360,7 @@
|
||||||
DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */,
|
DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */,
|
||||||
DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */,
|
DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */,
|
||||||
DC0DF86A0F0DAAF500B0F1F3 /* GlobalPropsDialog.hxx in Headers */,
|
DC0DF86A0F0DAAF500B0F1F3 /* GlobalPropsDialog.hxx in Headers */,
|
||||||
|
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */,
|
||||||
DC5D2C520F117CFD004D1660 /* Rect.hxx in Headers */,
|
DC5D2C520F117CFD004D1660 /* Rect.hxx in Headers */,
|
||||||
DC71EAA81FDA070D008827CB /* CartMNetworkWidget.hxx in Headers */,
|
DC71EAA81FDA070D008827CB /* CartMNetworkWidget.hxx in Headers */,
|
||||||
DC5D2C530F117CFD004D1660 /* StellaFont.hxx in Headers */,
|
DC5D2C530F117CFD004D1660 /* StellaFont.hxx in Headers */,
|
||||||
|
@ -2309,7 +2372,6 @@
|
||||||
DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */,
|
DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */,
|
||||||
DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */,
|
DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */,
|
||||||
DC9EA8880F729A36000452B5 /* KidVid.hxx in Headers */,
|
DC9EA8880F729A36000452B5 /* KidVid.hxx in Headers */,
|
||||||
DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */,
|
|
||||||
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */,
|
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */,
|
||||||
DCBDDE9F1D6A5F2F009DF1E9 /* Cart3EPlus.hxx in Headers */,
|
DCBDDE9F1D6A5F2F009DF1E9 /* Cart3EPlus.hxx in Headers */,
|
||||||
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */,
|
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */,
|
||||||
|
@ -2350,6 +2412,7 @@
|
||||||
DC74D6A2138D4D7E00F05C5C /* StringParser.hxx in Headers */,
|
DC74D6A2138D4D7E00F05C5C /* StringParser.hxx in Headers */,
|
||||||
DC2874071F8F2278004BF21A /* TrapArray.hxx in Headers */,
|
DC2874071F8F2278004BF21A /* TrapArray.hxx in Headers */,
|
||||||
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */,
|
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */,
|
||||||
|
DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */,
|
||||||
DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */,
|
DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */,
|
||||||
DC8C1BAE14B25DE7006440EE /* CartCM.hxx in Headers */,
|
DC8C1BAE14B25DE7006440EE /* CartCM.hxx in Headers */,
|
||||||
DCF3A6FB1DFC75E3008A8AF3 /* Player.hxx in Headers */,
|
DCF3A6FB1DFC75E3008A8AF3 /* Player.hxx in Headers */,
|
||||||
|
@ -2386,6 +2449,7 @@
|
||||||
DC6DC921205DB879004A5FC3 /* PJoystickHandler.hxx in Headers */,
|
DC6DC921205DB879004A5FC3 /* PJoystickHandler.hxx in Headers */,
|
||||||
DCAAE5DF1715887B0080BB82 /* CartEFSCWidget.hxx in Headers */,
|
DCAAE5DF1715887B0080BB82 /* CartEFSCWidget.hxx in Headers */,
|
||||||
DCAAE5E11715887B0080BB82 /* CartEFWidget.hxx in Headers */,
|
DCAAE5E11715887B0080BB82 /* CartEFWidget.hxx in Headers */,
|
||||||
|
DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */,
|
||||||
DCAAE5E31715887B0080BB82 /* CartF0Widget.hxx in Headers */,
|
DCAAE5E31715887B0080BB82 /* CartF0Widget.hxx in Headers */,
|
||||||
DCAAE5E51715887B0080BB82 /* CartF4SCWidget.hxx in Headers */,
|
DCAAE5E51715887B0080BB82 /* CartF4SCWidget.hxx in Headers */,
|
||||||
DCAAE5E71715887B0080BB82 /* CartF4Widget.hxx in Headers */,
|
DCAAE5E71715887B0080BB82 /* CartF4Widget.hxx in Headers */,
|
||||||
|
@ -2551,6 +2615,7 @@
|
||||||
DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */,
|
DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */,
|
||||||
2D91747609BA90380026E9FF /* Cart.cxx in Sources */,
|
2D91747609BA90380026E9FF /* Cart.cxx in Sources */,
|
||||||
2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */,
|
2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */,
|
||||||
|
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */,
|
||||||
2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */,
|
2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */,
|
||||||
2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */,
|
2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */,
|
||||||
2D91747A09BA90380026E9FF /* CartAR.cxx in Sources */,
|
2D91747A09BA90380026E9FF /* CartAR.cxx in Sources */,
|
||||||
|
@ -2559,9 +2624,11 @@
|
||||||
DCF3A6FC1DFC75E3008A8AF3 /* Playfield.cxx in Sources */,
|
DCF3A6FC1DFC75E3008A8AF3 /* Playfield.cxx in Sources */,
|
||||||
2D91747C09BA90380026E9FF /* CartDPC.cxx in Sources */,
|
2D91747C09BA90380026E9FF /* CartDPC.cxx in Sources */,
|
||||||
2D91747D09BA90380026E9FF /* CartE0.cxx in Sources */,
|
2D91747D09BA90380026E9FF /* CartE0.cxx in Sources */,
|
||||||
|
E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */,
|
||||||
2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */,
|
2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */,
|
||||||
DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */,
|
DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */,
|
||||||
2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */,
|
2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */,
|
||||||
|
DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */,
|
||||||
2D91748009BA90380026E9FF /* CartF4SC.cxx in Sources */,
|
2D91748009BA90380026E9FF /* CartF4SC.cxx in Sources */,
|
||||||
2D91748109BA90380026E9FF /* CartF6.cxx in Sources */,
|
2D91748109BA90380026E9FF /* CartF6.cxx in Sources */,
|
||||||
2D91748209BA90380026E9FF /* CartF6SC.cxx in Sources */,
|
2D91748209BA90380026E9FF /* CartF6SC.cxx in Sources */,
|
||||||
|
@ -2578,6 +2645,7 @@
|
||||||
2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */,
|
2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */,
|
||||||
2D91749009BA90380026E9FF /* M6532.cxx in Sources */,
|
2D91749009BA90380026E9FF /* M6532.cxx in Sources */,
|
||||||
2D91749109BA90380026E9FF /* MD5.cxx in Sources */,
|
2D91749109BA90380026E9FF /* MD5.cxx in Sources */,
|
||||||
|
E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */,
|
||||||
DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */,
|
DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */,
|
||||||
2D91749309BA90380026E9FF /* Paddles.cxx in Sources */,
|
2D91749309BA90380026E9FF /* Paddles.cxx in Sources */,
|
||||||
2D91749409BA90380026E9FF /* Props.cxx in Sources */,
|
2D91749409BA90380026E9FF /* Props.cxx in Sources */,
|
||||||
|
@ -2650,6 +2718,7 @@
|
||||||
2D9174FA09BA90380026E9FF /* DebuggerDialog.cxx in Sources */,
|
2D9174FA09BA90380026E9FF /* DebuggerDialog.cxx in Sources */,
|
||||||
DCF3A6E91DFC75E3008A8AF3 /* Ball.cxx in Sources */,
|
DCF3A6E91DFC75E3008A8AF3 /* Ball.cxx in Sources */,
|
||||||
2D9174FB09BA90380026E9FF /* PromptWidget.cxx in Sources */,
|
2D9174FB09BA90380026E9FF /* PromptWidget.cxx in Sources */,
|
||||||
|
DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */,
|
||||||
2D9174FC09BA90380026E9FF /* RamWidget.cxx in Sources */,
|
2D9174FC09BA90380026E9FF /* RamWidget.cxx in Sources */,
|
||||||
DC2AADAE194F389C0026C7A4 /* CartDASH.cxx in Sources */,
|
DC2AADAE194F389C0026C7A4 /* CartDASH.cxx in Sources */,
|
||||||
2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */,
|
2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */,
|
||||||
|
@ -2668,7 +2737,7 @@
|
||||||
DC71EA9D1FDA06D2008827CB /* CartE78K.cxx in Sources */,
|
DC71EA9D1FDA06D2008827CB /* CartE78K.cxx in Sources */,
|
||||||
DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */,
|
DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */,
|
||||||
DCDDEAC41F5DBF0400C67366 /* RewindManager.cxx in Sources */,
|
DCDDEAC41F5DBF0400C67366 /* RewindManager.cxx in Sources */,
|
||||||
2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */,
|
E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */,
|
||||||
DC71EA9F1FDA06D2008827CB /* CartMNetwork.cxx in Sources */,
|
DC71EA9F1FDA06D2008827CB /* CartMNetwork.cxx in Sources */,
|
||||||
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */,
|
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */,
|
||||||
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */,
|
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */,
|
||||||
|
@ -2753,6 +2822,7 @@
|
||||||
DC8C1BAD14B25DE7006440EE /* CartCM.cxx in Sources */,
|
DC8C1BAD14B25DE7006440EE /* CartCM.cxx in Sources */,
|
||||||
DCDDEAC61F5DBF0400C67366 /* StateManager.cxx in Sources */,
|
DCDDEAC61F5DBF0400C67366 /* StateManager.cxx in Sources */,
|
||||||
DC8C1BAF14B25DE7006440EE /* CompuMate.cxx in Sources */,
|
DC8C1BAF14B25DE7006440EE /* CompuMate.cxx in Sources */,
|
||||||
|
E09F4142201E9050004A3391 /* Audio.cxx in Sources */,
|
||||||
DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */,
|
DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */,
|
||||||
DCCF47DF14B60DEE00814FAB /* JoystickWidget.cxx in Sources */,
|
DCCF47DF14B60DEE00814FAB /* JoystickWidget.cxx in Sources */,
|
||||||
DCCF49B714B7544A00814FAB /* PaddleWidget.cxx in Sources */,
|
DCCF49B714B7544A00814FAB /* PaddleWidget.cxx in Sources */,
|
||||||
|
@ -2774,6 +2844,7 @@
|
||||||
DC3EE8601E2C0E6D00905161 /* gzwrite.c in Sources */,
|
DC3EE8601E2C0E6D00905161 /* gzwrite.c in Sources */,
|
||||||
DCAAE5D71715887B0080BB82 /* Cart4KWidget.cxx in Sources */,
|
DCAAE5D71715887B0080BB82 /* Cart4KWidget.cxx in Sources */,
|
||||||
DCDA03B01A2009BB00711920 /* CartWD.cxx in Sources */,
|
DCDA03B01A2009BB00711920 /* CartWD.cxx in Sources */,
|
||||||
|
DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */,
|
||||||
DCAAE5D91715887B0080BB82 /* Cart0840Widget.cxx in Sources */,
|
DCAAE5D91715887B0080BB82 /* Cart0840Widget.cxx in Sources */,
|
||||||
DCAAE5DB1715887B0080BB82 /* CartCVWidget.cxx in Sources */,
|
DCAAE5DB1715887B0080BB82 /* CartCVWidget.cxx in Sources */,
|
||||||
DCAAE5DE1715887B0080BB82 /* CartEFSCWidget.cxx in Sources */,
|
DCAAE5DE1715887B0080BB82 /* CartEFSCWidget.cxx in Sources */,
|
||||||
|
@ -2802,6 +2873,7 @@
|
||||||
DC676A4D1729A0B000E4E73D /* CartDPCWidget.cxx in Sources */,
|
DC676A4D1729A0B000E4E73D /* CartDPCWidget.cxx in Sources */,
|
||||||
DC676A4F1729A0B000E4E73D /* CartE0Widget.cxx in Sources */,
|
DC676A4F1729A0B000E4E73D /* CartE0Widget.cxx in Sources */,
|
||||||
DC676A511729A0B000E4E73D /* CartE7Widget.cxx in Sources */,
|
DC676A511729A0B000E4E73D /* CartE7Widget.cxx in Sources */,
|
||||||
|
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */,
|
||||||
DC676A531729A0B000E4E73D /* CartFA2Widget.cxx in Sources */,
|
DC676A531729A0B000E4E73D /* CartFA2Widget.cxx in Sources */,
|
||||||
DC676A551729A0B000E4E73D /* CartFEWidget.cxx in Sources */,
|
DC676A551729A0B000E4E73D /* CartFEWidget.cxx in Sources */,
|
||||||
DC676A591729A0B000E4E73D /* CartSBWidget.cxx in Sources */,
|
DC676A591729A0B000E4E73D /* CartSBWidget.cxx in Sources */,
|
||||||
|
|
|
@ -20,6 +20,4 @@
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
SettingsWINDOWS::SettingsWINDOWS(OSystem& osystem)
|
SettingsWINDOWS::SettingsWINDOWS(OSystem& osystem)
|
||||||
: Settings(osystem)
|
: Settings(osystem)
|
||||||
{
|
{}
|
||||||
setInternal("fragsize", "1024");
|
|
||||||
}
|
|
||||||
|
|
|
@ -228,6 +228,10 @@
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<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\Base.cxx" />
|
||||||
<ClCompile Include="..\common\EventHandlerSDL2.cxx" />
|
<ClCompile Include="..\common\EventHandlerSDL2.cxx" />
|
||||||
<ClCompile Include="..\common\FBSurfaceSDL2.cxx" />
|
<ClCompile Include="..\common\FBSurfaceSDL2.cxx" />
|
||||||
|
@ -323,10 +327,15 @@
|
||||||
<ClCompile Include="..\emucore\CartE78K.cxx" />
|
<ClCompile Include="..\emucore\CartE78K.cxx" />
|
||||||
<ClCompile Include="..\emucore\CartWD.cxx" />
|
<ClCompile Include="..\emucore\CartWD.cxx" />
|
||||||
<ClCompile Include="..\emucore\CompuMate.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\FBSurface.cxx" />
|
||||||
<ClCompile Include="..\emucore\MindLink.cxx" />
|
<ClCompile Include="..\emucore\MindLink.cxx" />
|
||||||
<ClCompile Include="..\emucore\PointingDevice.cxx" />
|
<ClCompile Include="..\emucore\PointingDevice.cxx" />
|
||||||
<ClCompile Include="..\emucore\TIASurface.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\Background.cxx" />
|
||||||
<ClCompile Include="..\emucore\tia\Ball.cxx" />
|
<ClCompile Include="..\emucore\tia\Ball.cxx" />
|
||||||
<ClCompile Include="..\emucore\tia\DrawCounterDecodes.cxx" />
|
<ClCompile Include="..\emucore\tia\DrawCounterDecodes.cxx" />
|
||||||
|
@ -410,7 +419,6 @@
|
||||||
<ClCompile Include="..\emucore\Switches.cxx" />
|
<ClCompile Include="..\emucore\Switches.cxx" />
|
||||||
<ClCompile Include="..\emucore\System.cxx" />
|
<ClCompile Include="..\emucore\System.cxx" />
|
||||||
<ClCompile Include="..\emucore\Thumbulator.cxx" />
|
<ClCompile Include="..\emucore\Thumbulator.cxx" />
|
||||||
<ClCompile Include="..\emucore\TIASnd.cxx" />
|
|
||||||
<ClCompile Include="..\cheat\BankRomCheat.cxx" />
|
<ClCompile Include="..\cheat\BankRomCheat.cxx" />
|
||||||
<ClCompile Include="..\cheat\CheatCodeDialog.cxx" />
|
<ClCompile Include="..\cheat\CheatCodeDialog.cxx" />
|
||||||
<ClCompile Include="..\cheat\CheatManager.cxx" />
|
<ClCompile Include="..\cheat\CheatManager.cxx" />
|
||||||
|
@ -511,6 +519,11 @@
|
||||||
<ClCompile Include="..\libpng\pngwutil.c" />
|
<ClCompile Include="..\libpng\pngwutil.c" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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\Base.hxx" />
|
||||||
<ClInclude Include="..\common\bspf.hxx" />
|
<ClInclude Include="..\common\bspf.hxx" />
|
||||||
<ClInclude Include="..\common\EventHandlerSDL2.hxx" />
|
<ClInclude Include="..\common\EventHandlerSDL2.hxx" />
|
||||||
|
@ -620,12 +633,17 @@
|
||||||
<ClInclude Include="..\emucore\CartE78K.hxx" />
|
<ClInclude Include="..\emucore\CartE78K.hxx" />
|
||||||
<ClInclude Include="..\emucore\CartWD.hxx" />
|
<ClInclude Include="..\emucore\CartWD.hxx" />
|
||||||
<ClInclude Include="..\emucore\CompuMate.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\EventHandlerConstants.hxx" />
|
||||||
<ClInclude Include="..\emucore\FBSurface.hxx" />
|
<ClInclude Include="..\emucore\FBSurface.hxx" />
|
||||||
<ClInclude Include="..\emucore\FrameBufferConstants.hxx" />
|
<ClInclude Include="..\emucore\FrameBufferConstants.hxx" />
|
||||||
<ClInclude Include="..\emucore\MindLink.hxx" />
|
<ClInclude Include="..\emucore\MindLink.hxx" />
|
||||||
<ClInclude Include="..\emucore\PointingDevice.hxx" />
|
<ClInclude Include="..\emucore\PointingDevice.hxx" />
|
||||||
<ClInclude Include="..\emucore\TIASurface.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\Background.hxx" />
|
||||||
<ClInclude Include="..\emucore\tia\Ball.hxx" />
|
<ClInclude Include="..\emucore\tia\Ball.hxx" />
|
||||||
<ClInclude Include="..\emucore\tia\DelayQueue.hxx" />
|
<ClInclude Include="..\emucore\tia\DelayQueue.hxx" />
|
||||||
|
@ -733,7 +751,6 @@
|
||||||
<ClInclude Include="..\emucore\Switches.hxx" />
|
<ClInclude Include="..\emucore\Switches.hxx" />
|
||||||
<ClInclude Include="..\emucore\System.hxx" />
|
<ClInclude Include="..\emucore\System.hxx" />
|
||||||
<ClInclude Include="..\emucore\Thumbulator.hxx" />
|
<ClInclude Include="..\emucore\Thumbulator.hxx" />
|
||||||
<ClInclude Include="..\emucore\TIASnd.hxx" />
|
|
||||||
<ClInclude Include="..\debugger\gui\AudioWidget.hxx" />
|
<ClInclude Include="..\debugger\gui\AudioWidget.hxx" />
|
||||||
<ClInclude Include="..\debugger\CartDebug.hxx" />
|
<ClInclude Include="..\debugger\CartDebug.hxx" />
|
||||||
<ClInclude Include="..\debugger\CpuDebug.hxx" />
|
<ClInclude Include="..\debugger\CpuDebug.hxx" />
|
||||||
|
|
|
@ -55,6 +55,12 @@
|
||||||
<Filter Include="Source Files\emucore\tia">
|
<Filter Include="Source Files\emucore\tia">
|
||||||
<UniqueIdentifier>{ffa3642d-aa8a-43a5-8ac5-acd8878dd091}</UniqueIdentifier>
|
<UniqueIdentifier>{ffa3642d-aa8a-43a5-8ac5-acd8878dd091}</UniqueIdentifier>
|
||||||
</Filter>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\common\FrameBufferSDL2.cxx">
|
<ClCompile Include="..\common\FrameBufferSDL2.cxx">
|
||||||
|
@ -237,9 +243,6 @@
|
||||||
<ClCompile Include="..\emucore\Thumbulator.cxx">
|
<ClCompile Include="..\emucore\Thumbulator.cxx">
|
||||||
<Filter>Source Files\emucore</Filter>
|
<Filter>Source Files\emucore</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\emucore\TIASnd.cxx">
|
|
||||||
<Filter>Source Files\emucore</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\cheat\BankRomCheat.cxx">
|
<ClCompile Include="..\cheat\BankRomCheat.cxx">
|
||||||
<Filter>Source Files\cheat</Filter>
|
<Filter>Source Files\cheat</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -900,6 +903,33 @@
|
||||||
<ClCompile Include="..\common\PKeyboardHandler.cxx">
|
<ClCompile Include="..\common\PKeyboardHandler.cxx">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\common\bspf.hxx">
|
<ClInclude Include="..\common\bspf.hxx">
|
||||||
|
@ -1112,9 +1142,6 @@
|
||||||
<ClInclude Include="..\emucore\Thumbulator.hxx">
|
<ClInclude Include="..\emucore\Thumbulator.hxx">
|
||||||
<Filter>Header Files\emucore</Filter>
|
<Filter>Header Files\emucore</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\emucore\TIASnd.hxx">
|
|
||||||
<Filter>Header Files\emucore</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\debugger\gui\AudioWidget.hxx">
|
<ClInclude Include="..\debugger\gui\AudioWidget.hxx">
|
||||||
<Filter>Header Files\debugger</Filter>
|
<Filter>Header Files\debugger</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -1844,6 +1871,36 @@
|
||||||
<ClInclude Include="..\common\PKeyboardHandler.hxx">
|
<ClInclude Include="..\common\PKeyboardHandler.hxx">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="stella.ico">
|
<None Include="stella.ico">
|
||||||
|
|
Loading…
Reference in New Issue