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",
|
||||
"system_error": "cpp",
|
||||
"vector": "cpp",
|
||||
"stdexcept": "cpp"
|
||||
"sstream": "cpp",
|
||||
"__bit_reference": "cpp",
|
||||
"__functional_base": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"bitset": "cpp",
|
||||
"chrono": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"limits": "cpp",
|
||||
"locale": "cpp",
|
||||
"memory": "cpp",
|
||||
"ratio": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"fstream": "cpp",
|
||||
"__locale": "cpp",
|
||||
"__string": "cpp",
|
||||
"__config": "cpp",
|
||||
"__nullptr": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"exception": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"new": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"__mutex_base": "cpp",
|
||||
"mutex": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"*.ins": "cpp",
|
||||
"cstring": "cpp",
|
||||
"iostream": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"ostream": "cpp",
|
||||
"__memory": "cpp"
|
||||
}
|
||||
}
|
||||
|
|
3
Makefile
3
Makefile
|
@ -101,7 +101,8 @@ MODULES += \
|
|||
src/emucore/tia/frame-manager \
|
||||
src/gui \
|
||||
src/common \
|
||||
src/common/tv_filters
|
||||
src/common/tv_filters \
|
||||
src/common/audio
|
||||
|
||||
######################################################################
|
||||
# The build rules follow - normally you should have no need to
|
||||
|
|
|
@ -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)
|
||||
if(myRenderer)
|
||||
{
|
||||
// Always clear the (double-buffered) renderer surface
|
||||
SDL_RenderClear(myRenderer);
|
||||
SDL_RenderPresent(myRenderer);
|
||||
SDL_RenderClear(myRenderer);
|
||||
SDL_DestroyRenderer(myRenderer);
|
||||
myRenderer = nullptr;
|
||||
}
|
||||
|
@ -234,6 +230,12 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
|
|||
myOSystem.logMessage(msg, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always clear the (double-buffered) renderer surface
|
||||
SDL_RenderClear(myRenderer);
|
||||
SDL_RenderPresent(myRenderer);
|
||||
SDL_RenderClear(myRenderer);
|
||||
|
||||
SDL_RendererInfo renderinfo;
|
||||
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
|
||||
myOSystem.settings().setValue("video", renderinfo.name);
|
||||
|
@ -241,6 +243,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
|
|||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void FrameBufferSDL2::setTitle(const string& title)
|
||||
{
|
||||
myScreenTitle = title;
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
#include "SoundNull.hxx"
|
||||
#endif
|
||||
|
||||
class AudioSettings;
|
||||
|
||||
/**
|
||||
This class deals with the different framebuffer/sound/event
|
||||
implementations for the various ports of Stella, and always returns a
|
||||
|
@ -108,10 +110,10 @@ class MediaFactory
|
|||
return make_unique<FrameBufferSDL2>(osystem);
|
||||
}
|
||||
|
||||
static unique_ptr<Sound> createAudio(OSystem& osystem)
|
||||
static unique_ptr<Sound> createAudio(OSystem& osystem, AudioSettings& audioSettings)
|
||||
{
|
||||
#ifdef SOUND_SUPPORT
|
||||
return make_unique<SoundSDL2>(osystem);
|
||||
return make_unique<SoundSDL2>(osystem, audioSettings);
|
||||
#else
|
||||
return make_unique<SoundNull>(osystem);
|
||||
#endif
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include "bspf.hxx"
|
||||
#include "Sound.hxx"
|
||||
#include "OSystem.hxx"
|
||||
#include "AudioQueue.hxx"
|
||||
#include "EmulationTiming.hxx"
|
||||
|
||||
/**
|
||||
This class implements a Null sound object, where-by sound generation
|
||||
|
@ -53,26 +55,11 @@ class SoundNull : public Sound
|
|||
*/
|
||||
void setEnabled(bool state) override { }
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound).
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
void setChannels(uInt32 channels) override { }
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
void setFrameRate(float framerate) override { }
|
||||
|
||||
/**
|
||||
Initializes the sound device. This must be called before any
|
||||
calls are made to derived methods.
|
||||
*/
|
||||
void open() override { }
|
||||
void open(shared_ptr<AudioQueue>, EmulationTiming*) override { }
|
||||
|
||||
/**
|
||||
Should be called to close the sound device. Once called the sound
|
||||
|
@ -92,15 +79,6 @@ class SoundNull : public Sound
|
|||
*/
|
||||
void reset() override { }
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
void set(uInt16 addr, uInt8 value, uInt64 cycle) override { }
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
|
@ -108,7 +86,7 @@ class SoundNull : public Sound
|
|||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
*/
|
||||
void setVolume(Int32 percent) override { }
|
||||
void setVolume(uInt32 percent) override { }
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
@ -118,54 +96,6 @@ class SoundNull : public Sound
|
|||
*/
|
||||
void adjustVolume(Int8 direction) override { }
|
||||
|
||||
public:
|
||||
/**
|
||||
Saves the current state of this device to the given Serializer.
|
||||
|
||||
@param out The serializer device to save to.
|
||||
@return The result of the save. True on success, false on failure.
|
||||
*/
|
||||
bool save(Serializer& out) const override
|
||||
{
|
||||
out.putString("TIASound");
|
||||
|
||||
for(int i = 0; i < 6; ++i)
|
||||
out.putByte(0);
|
||||
|
||||
// myLastRegisterSetCycle
|
||||
out.putInt(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Loads the current state of this device from the given Serializer.
|
||||
|
||||
@param in The Serializer device to load from.
|
||||
@return The result of the load. True on success, false on failure.
|
||||
*/
|
||||
bool load(Serializer& in) override
|
||||
{
|
||||
if(in.getString() != "TIASound")
|
||||
return false;
|
||||
|
||||
// Read sound registers and discard
|
||||
for(int i = 0; i < 6; ++i)
|
||||
in.getByte();
|
||||
|
||||
// myLastRegisterSetCycle
|
||||
in.getInt();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Get a descriptor for this console class (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const override { return "TIASound"; }
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
SoundNull() = delete;
|
||||
|
|
|
@ -22,46 +22,38 @@
|
|||
#include <cmath>
|
||||
|
||||
#include "SDL_lib.hxx"
|
||||
#include "TIASnd.hxx"
|
||||
#include "TIAConstants.hxx"
|
||||
#include "FrameBuffer.hxx"
|
||||
#include "Settings.hxx"
|
||||
#include "System.hxx"
|
||||
#include "OSystem.hxx"
|
||||
#include "Console.hxx"
|
||||
#include "SoundSDL2.hxx"
|
||||
#include "AudioQueue.hxx"
|
||||
#include "EmulationTiming.hxx"
|
||||
#include "AudioSettings.hxx"
|
||||
#include "audio/SimpleResampler.hxx"
|
||||
#include "audio/LanczosResampler.hxx"
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::SoundSDL2(OSystem& osystem)
|
||||
SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings)
|
||||
: Sound(osystem),
|
||||
myIsEnabled(false),
|
||||
myIsInitializedFlag(false),
|
||||
myLastRegisterSetCycle(0),
|
||||
myNumChannels(0),
|
||||
myFragmentSizeLogBase2(0),
|
||||
myFragmentSizeLogDiv1(0),
|
||||
myFragmentSizeLogDiv2(0),
|
||||
myIsMuted(true),
|
||||
myVolume(100)
|
||||
myVolume(100),
|
||||
myVolumeFactor(0xffff),
|
||||
myCurrentFragment(nullptr),
|
||||
myAudioSettings(audioSettings)
|
||||
{
|
||||
myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2);
|
||||
|
||||
#ifdef BSPF_WINDOWS
|
||||
// TODO - remove the following code once we convert to the new sound
|
||||
// core, and use 32-bit floating point samples and do
|
||||
// our own resampling
|
||||
SDL_setenv("SDL_AUDIODRIVER", "directsound", true);
|
||||
#endif
|
||||
|
||||
// The sound system is opened only once per program run, to eliminate
|
||||
// issues with opening and closing it multiple times
|
||||
// This fixes a bug most prevalent with ATI video cards in Windows,
|
||||
// whereby sound stopped working after the first video change
|
||||
SDL_AudioSpec desired;
|
||||
desired.freq = myOSystem.settings().getInt("freq");
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.freq = myAudioSettings.sampleRate();
|
||||
desired.format = AUDIO_F32SYS;
|
||||
desired.channels = 2;
|
||||
desired.samples = myOSystem.settings().getInt("fragsize");
|
||||
desired.samples = myAudioSettings.fragmentSize();
|
||||
desired.callback = callback;
|
||||
desired.userdata = static_cast<void*>(this);
|
||||
|
||||
|
@ -87,13 +79,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
|||
return;
|
||||
}
|
||||
|
||||
// Pre-compute fragment-related variables as much as possible
|
||||
myFragmentSizeLogBase2 = log(myHardwareSpec.samples) / log(2.0);
|
||||
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / 60.0;
|
||||
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / 60.0;
|
||||
|
||||
myIsInitializedFlag = true;
|
||||
SDL_PauseAudio(1);
|
||||
|
||||
mute(true);
|
||||
|
||||
myOSystem.logMessage("SoundSDL2::SoundSDL2 initialized", 2);
|
||||
}
|
||||
|
@ -101,43 +89,43 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::~SoundSDL2()
|
||||
{
|
||||
// Close the SDL audio system if it's initialized
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_CloseAudio();
|
||||
myIsEnabled = myIsInitializedFlag = false;
|
||||
}
|
||||
if (!myIsInitializedFlag) return;
|
||||
|
||||
SDL_CloseAudio();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setEnabled(bool state)
|
||||
{
|
||||
myOSystem.settings().setValue("sound", state);
|
||||
myAudioSettings.setEnabled(state);
|
||||
if (myAudioQueue) myAudioQueue->ignoreOverflows(!state);
|
||||
|
||||
myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" :
|
||||
"SoundSDL2::setEnabled(false)", 2);
|
||||
"SoundSDL2::setEnabled(false)", 2);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::open()
|
||||
void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
|
||||
EmulationTiming* emulationTiming)
|
||||
{
|
||||
myEmulationTiming = emulationTiming;
|
||||
|
||||
myOSystem.logMessage("SoundSDL2::open started ...", 2);
|
||||
myIsEnabled = false;
|
||||
mute(true);
|
||||
if(!myIsInitializedFlag || !myOSystem.settings().getBool("sound"))
|
||||
|
||||
audioQueue->ignoreOverflows(!myAudioSettings.enabled());
|
||||
if(!myAudioSettings.enabled())
|
||||
{
|
||||
myOSystem.logMessage("Sound disabled\n", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now initialize the TIASound object which will actually generate sound
|
||||
myTIASound.outputFrequency(myHardwareSpec.freq);
|
||||
const string& chanResult =
|
||||
myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2);
|
||||
myAudioQueue = audioQueue;
|
||||
myUnderrun = true;
|
||||
myCurrentFragment = nullptr;
|
||||
|
||||
// Adjust volume to that defined in settings
|
||||
myVolume = myOSystem.settings().getInt("volume");
|
||||
setVolume(myVolume);
|
||||
setVolume(myAudioSettings.volume());
|
||||
|
||||
// Show some info
|
||||
ostringstream buf;
|
||||
|
@ -146,12 +134,12 @@ void SoundSDL2::open()
|
|||
<< " Frag size: " << uInt32(myHardwareSpec.samples) << endl
|
||||
<< " Frequency: " << uInt32(myHardwareSpec.freq) << endl
|
||||
<< " Channels: " << uInt32(myHardwareSpec.channels)
|
||||
<< " (" << chanResult << ")" << endl
|
||||
<< endl;
|
||||
myOSystem.logMessage(buf.str(), 1);
|
||||
|
||||
initResampler();
|
||||
|
||||
// And start the SDL sound subsystem ...
|
||||
myIsEnabled = true;
|
||||
mute(false);
|
||||
|
||||
myOSystem.logMessage("SoundSDL2::open finished", 2);
|
||||
|
@ -160,15 +148,16 @@ void SoundSDL2::open()
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::close()
|
||||
{
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
myIsEnabled = false;
|
||||
SDL_PauseAudio(1);
|
||||
myLastRegisterSetCycle = 0;
|
||||
myTIASound.reset();
|
||||
myRegWriteQueue.clear();
|
||||
myOSystem.logMessage("SoundSDL2::close", 2);
|
||||
}
|
||||
if(!myIsInitializedFlag) return;
|
||||
|
||||
mute(true);
|
||||
|
||||
if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment);
|
||||
myAudioQueue.reset();
|
||||
myCurrentFragment = nullptr;
|
||||
|
||||
myOSystem.logMessage("SoundSDL2::close", 2);
|
||||
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -176,33 +165,25 @@ void SoundSDL2::mute(bool state)
|
|||
{
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
myIsMuted = state;
|
||||
SDL_PauseAudio(myIsMuted ? 1 : 0);
|
||||
SDL_PauseAudio(state ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::reset()
|
||||
{
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_PauseAudio(1);
|
||||
myLastRegisterSetCycle = 0;
|
||||
myTIASound.reset();
|
||||
myRegWriteQueue.clear();
|
||||
mute(myIsMuted);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setVolume(Int32 percent)
|
||||
void SoundSDL2::setVolume(uInt32 percent)
|
||||
{
|
||||
if(myIsInitializedFlag && (percent >= 0) && (percent <= 100))
|
||||
if(myIsInitializedFlag && (percent <= 100))
|
||||
{
|
||||
myOSystem.settings().setValue("volume", percent);
|
||||
SDL_LockAudio();
|
||||
myAudioSettings.setVolume(percent);
|
||||
myVolume = percent;
|
||||
myTIASound.volume(percent);
|
||||
|
||||
SDL_LockAudio();
|
||||
myVolumeFactor = std::pow(static_cast<float>(percent) / 100.f, 2.f);
|
||||
SDL_UnlockAudio();
|
||||
}
|
||||
}
|
||||
|
@ -234,285 +215,80 @@ void SoundSDL2::adjustVolume(Int8 direction)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setChannels(uInt32 channels)
|
||||
uInt32 SoundSDL2::getFragmentSize() const
|
||||
{
|
||||
if(channels == 1 || channels == 2)
|
||||
myNumChannels = channels;
|
||||
return myHardwareSpec.samples;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setFrameRate(float framerate)
|
||||
uInt32 SoundSDL2::getSampleRate() const
|
||||
{
|
||||
// Recalculate since frame rate has changed
|
||||
// FIXME - should we clear out the queue or adjust the values in it?
|
||||
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / framerate;
|
||||
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / framerate;
|
||||
return myHardwareSpec.freq;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::set(uInt16 addr, uInt8 value, uInt64 cycle)
|
||||
void SoundSDL2::processFragment(float* stream, uInt32 length)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
myResampler->fillFragment(stream, length);
|
||||
|
||||
// First, calculate how many seconds would have past since the last
|
||||
// register write on a real 2600
|
||||
double delta = double(cycle - myLastRegisterSetCycle) / 1193191.66666667;
|
||||
|
||||
// Now, adjust the time based on the frame rate the user has selected. For
|
||||
// the sound to "scale" correctly, we have to know the games real frame
|
||||
// rate (e.g., 50 or 60) and the currently emulated frame rate. We use these
|
||||
// values to "scale" the time before the register change occurs.
|
||||
myRegWriteQueue.enqueue(addr, value, delta);
|
||||
|
||||
// Update last cycle counter to the current cycle
|
||||
myLastRegisterSetCycle = cycle;
|
||||
|
||||
SDL_UnlockAudio();
|
||||
for (uInt32 i = 0; i < length; i++) stream[i] = stream[i] * myVolumeFactor;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::processFragment(Int16* stream, uInt32 length)
|
||||
void SoundSDL2::initResampler()
|
||||
{
|
||||
uInt32 channels = myHardwareSpec.channels;
|
||||
length = length / channels;
|
||||
Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
|
||||
Int16* nextFragment = nullptr;
|
||||
|
||||
// If there are excessive items on the queue then we'll remove some
|
||||
if(myRegWriteQueue.duration() > myFragmentSizeLogDiv1)
|
||||
{
|
||||
double removed = 0.0;
|
||||
while(removed < myFragmentSizeLogDiv2)
|
||||
{
|
||||
RegWrite& info = myRegWriteQueue.front();
|
||||
removed += info.delta;
|
||||
myTIASound.set(info.addr, info.value);
|
||||
myRegWriteQueue.dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
double position = 0.0;
|
||||
double remaining = length;
|
||||
|
||||
while(remaining > 0.0)
|
||||
{
|
||||
if(myRegWriteQueue.size() == 0)
|
||||
{
|
||||
// There are no more pending TIA sound register updates so we'll
|
||||
// use the current settings to finish filling the sound fragment
|
||||
myTIASound.process(stream + (uInt32(position) * channels),
|
||||
length - uInt32(position));
|
||||
|
||||
// Since we had to fill the fragment we'll reset the cycle counter
|
||||
// to zero. NOTE: This isn't 100% correct, however, it'll do for
|
||||
// now. We should really remember the overrun and remove it from
|
||||
// the delta of the next write.
|
||||
myLastRegisterSetCycle = 0;
|
||||
break;
|
||||
}
|
||||
if (myUnderrun)
|
||||
nextFragment = myAudioQueue->size() > myEmulationTiming->prebufferFragmentCount() ?
|
||||
myAudioQueue->dequeue(myCurrentFragment) : nullptr;
|
||||
else
|
||||
{
|
||||
// There are pending TIA sound register updates so we need to
|
||||
// update the sound buffer to the point of the next register update
|
||||
RegWrite& info = myRegWriteQueue.front();
|
||||
nextFragment = myAudioQueue->dequeue(myCurrentFragment);
|
||||
|
||||
// How long will the remaining samples in the fragment take to play
|
||||
double duration = remaining / myHardwareSpec.freq;
|
||||
myUnderrun = nextFragment == nullptr;
|
||||
if (nextFragment) myCurrentFragment = nextFragment;
|
||||
|
||||
// Does the register update occur before the end of the fragment?
|
||||
if(info.delta <= duration)
|
||||
{
|
||||
// If the register update time hasn't already passed then
|
||||
// process samples upto the point where it should occur
|
||||
if(info.delta > 0.0)
|
||||
{
|
||||
// Process the fragment upto the next TIA register write. We
|
||||
// round the count passed to process up if needed.
|
||||
double samples = (myHardwareSpec.freq * info.delta);
|
||||
myTIASound.process(stream + (uInt32(position) * channels),
|
||||
uInt32(samples) + uInt32(position + samples) -
|
||||
(uInt32(position) + uInt32(samples)));
|
||||
return nextFragment;
|
||||
};
|
||||
|
||||
position += samples;
|
||||
remaining -= samples;
|
||||
}
|
||||
myTIASound.set(info.addr, info.value);
|
||||
myRegWriteQueue.dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The next register update occurs in the next fragment so finish
|
||||
// this fragment with the current TIA settings and reduce the register
|
||||
// update delay by the corresponding amount of time
|
||||
myTIASound.process(stream + (uInt32(position) * channels),
|
||||
length - uInt32(position));
|
||||
info.delta -= duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Resampler::Format formatFrom =
|
||||
Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo());
|
||||
Resampler::Format formatTo =
|
||||
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1);
|
||||
|
||||
|
||||
switch (myAudioSettings.resamplingQuality()) {
|
||||
case AudioSettings::ResamplingQuality::nearestNeightbour:
|
||||
myResampler = make_unique<SimpleResampler>(formatFrom, formatTo, nextFragmentCallback);
|
||||
(cerr << "resampling quality 1: using nearest neighbor resampling\n").flush();
|
||||
break;
|
||||
|
||||
case AudioSettings::ResamplingQuality::lanczos_2:
|
||||
(cerr << "resampling quality 2: using nearest Lanczos resampling, a = 2\n").flush();
|
||||
myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 2);
|
||||
break;
|
||||
|
||||
case AudioSettings::ResamplingQuality::lanczos_3:
|
||||
(cerr << "resampling quality 3: using nearest Lanczos resampling, a = 3\n").flush();
|
||||
myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 3);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw runtime_error("invalid resampling quality");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::callback(void* udata, uInt8* stream, int len)
|
||||
{
|
||||
SoundSDL2* sound = static_cast<SoundSDL2*>(udata);
|
||||
if(sound->myIsEnabled)
|
||||
{
|
||||
// The callback is requesting 8-bit (unsigned) data, but the TIA sound
|
||||
// emulator deals in 16-bit (signed) data
|
||||
// So, we need to convert the pointer and half the length
|
||||
sound->processFragment(reinterpret_cast<Int16*>(stream), uInt32(len) >> 1);
|
||||
}
|
||||
SoundSDL2* self = static_cast<SoundSDL2*>(udata);
|
||||
|
||||
if (self->myAudioQueue)
|
||||
self->processFragment(reinterpret_cast<float*>(stream), len >> 2);
|
||||
else
|
||||
SDL_memset(stream, 0, len); // Write 'silence'
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool SoundSDL2::save(Serializer& out) const
|
||||
{
|
||||
try
|
||||
{
|
||||
out.putString(name());
|
||||
|
||||
// Only get the TIA sound registers if sound is enabled
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV1));
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
out.putByte(0);
|
||||
|
||||
out.putLong(myLastRegisterSetCycle);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::save", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool SoundSDL2::load(Serializer& in)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(in.getString() != name())
|
||||
return false;
|
||||
|
||||
// Only update the TIA sound registers if sound is enabled
|
||||
// Make sure to empty the queue of previous sound fragments
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_PauseAudio(1);
|
||||
myRegWriteQueue.clear();
|
||||
myTIASound.set(TIARegister::AUDC0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDC1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV1, in.getByte());
|
||||
if(!myIsMuted) SDL_PauseAudio(0);
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
in.getByte();
|
||||
|
||||
myLastRegisterSetCycle = in.getLong();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::load", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::RegWriteQueue::RegWriteQueue(uInt32 capacity)
|
||||
: myBuffer(make_unique<RegWrite[]>(capacity)),
|
||||
myCapacity(capacity),
|
||||
mySize(0),
|
||||
myHead(0),
|
||||
myTail(0)
|
||||
{
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::clear()
|
||||
{
|
||||
myHead = myTail = mySize = 0;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::dequeue()
|
||||
{
|
||||
if(mySize > 0)
|
||||
{
|
||||
myHead = (myHead + 1) % myCapacity;
|
||||
--mySize;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
double SoundSDL2::RegWriteQueue::duration() const
|
||||
{
|
||||
double duration = 0.0;
|
||||
for(uInt32 i = 0; i < mySize; ++i)
|
||||
{
|
||||
duration += myBuffer[(myHead + i) % myCapacity].delta;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::enqueue(uInt16 addr, uInt8 value, double delta)
|
||||
{
|
||||
// If an attempt is made to enqueue more than the queue can hold then
|
||||
// we'll enlarge the queue's capacity.
|
||||
if(mySize == myCapacity)
|
||||
grow();
|
||||
|
||||
RegWrite& reg = myBuffer[myTail];
|
||||
reg.addr = addr;
|
||||
reg.value = value;
|
||||
reg.delta = delta;
|
||||
myTail = (myTail + 1) % myCapacity;
|
||||
++mySize;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::RegWrite& SoundSDL2::RegWriteQueue::front() const
|
||||
{
|
||||
assert(mySize != 0);
|
||||
return myBuffer[myHead];
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt32 SoundSDL2::RegWriteQueue::size() const
|
||||
{
|
||||
return mySize;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::grow()
|
||||
{
|
||||
unique_ptr<RegWrite[]> buffer = make_unique<RegWrite[]>(myCapacity*2);
|
||||
for(uInt32 i = 0; i < mySize; ++i)
|
||||
buffer[i] = myBuffer[(myHead + i) % myCapacity];
|
||||
|
||||
myHead = 0;
|
||||
myTail = mySize;
|
||||
myCapacity *= 2;
|
||||
|
||||
myBuffer = std::move(buffer);
|
||||
SDL_memset(stream, 0, len);
|
||||
}
|
||||
|
||||
#endif // SOUND_SUPPORT
|
||||
|
|
|
@ -21,17 +21,20 @@
|
|||
#define SOUND_SDL2_HXX
|
||||
|
||||
class OSystem;
|
||||
class AudioQueue;
|
||||
class EmulationTiming;
|
||||
class AudioSettings;
|
||||
|
||||
#include "SDL_lib.hxx"
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "TIASnd.hxx"
|
||||
#include "Sound.hxx"
|
||||
#include "audio/Resampler.hxx"
|
||||
|
||||
/**
|
||||
This class implements the sound API for SDL.
|
||||
|
||||
@author Stephen Anthony and Bradford W. Mott
|
||||
@author Stephen Anthony and Christian Speckner (DirtyHairy)
|
||||
*/
|
||||
class SoundSDL2 : public Sound
|
||||
{
|
||||
|
@ -40,7 +43,7 @@ class SoundSDL2 : public Sound
|
|||
Create a new sound object. The init method must be invoked before
|
||||
using the object.
|
||||
*/
|
||||
SoundSDL2(OSystem& osystem);
|
||||
SoundSDL2(OSystem& osystem, AudioSettings& audioSettings);
|
||||
|
||||
/**
|
||||
Destructor
|
||||
|
@ -51,34 +54,15 @@ class SoundSDL2 : public Sound
|
|||
/**
|
||||
Enables/disables the sound subsystem.
|
||||
|
||||
@param state True or false, to enable or disable the sound system
|
||||
@param enable Either true or false, to enable or disable the sound system
|
||||
*/
|
||||
void setEnabled(bool state) override;
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound). Note that this
|
||||
determines how the emulation should 'mix' the channels of the TIA sound
|
||||
system (of which there are always two). It does not specify the actual
|
||||
number of hardware channels that SDL should use; it will always attempt
|
||||
to use two channels in hardware.
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
void setChannels(uInt32 channels) override;
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
void setFrameRate(float framerate) override;
|
||||
void setEnabled(bool enable) override;
|
||||
|
||||
/**
|
||||
Initializes the sound device. This must be called before any
|
||||
calls are made to derived methods.
|
||||
*/
|
||||
void open() override;
|
||||
void open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulationTiming) override;
|
||||
|
||||
/**
|
||||
Should be called to close the sound device. Once called the sound
|
||||
|
@ -98,15 +82,6 @@ class SoundSDL2 : public Sound
|
|||
*/
|
||||
void reset() override;
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
void set(uInt16 addr, uInt8 value, uInt64 cycle) override;
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
|
@ -114,7 +89,7 @@ class SoundSDL2 : public Sound
|
|||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
*/
|
||||
void setVolume(Int32 percent) override;
|
||||
void setVolume(uInt32 percent) override;
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
@ -124,29 +99,9 @@ class SoundSDL2 : public Sound
|
|||
*/
|
||||
void adjustVolume(Int8 direction) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
Saves the current state of this device to the given Serializer.
|
||||
uInt32 getFragmentSize() const override;
|
||||
|
||||
@param out The serializer device to save to.
|
||||
@return The result of the save. True on success, false on failure.
|
||||
*/
|
||||
bool save(Serializer& out) const override;
|
||||
|
||||
/**
|
||||
Loads the current state of this device from the given Serializer.
|
||||
|
||||
@param in The Serializer device to load from.
|
||||
@return The result of the load. True on success, false on failure.
|
||||
*/
|
||||
bool load(Serializer& in) override;
|
||||
|
||||
/**
|
||||
Get a descriptor for this console class (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const override { return "TIASound"; }
|
||||
uInt32 getSampleRate() const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
|
@ -157,124 +112,33 @@ class SoundSDL2 : public Sound
|
|||
@param stream Pointer to the start of the fragment
|
||||
@param length Length of the fragment
|
||||
*/
|
||||
void processFragment(Int16* stream, uInt32 length);
|
||||
|
||||
protected:
|
||||
// Struct to hold information regarding a TIA sound register write
|
||||
struct RegWrite
|
||||
{
|
||||
uInt16 addr;
|
||||
uInt8 value;
|
||||
double delta;
|
||||
|
||||
RegWrite(uInt16 a = 0, uInt8 v = 0, double d = 0.0)
|
||||
: addr(a), value(v), delta(d) { }
|
||||
};
|
||||
|
||||
/**
|
||||
A queue class used to hold TIA sound register writes before being
|
||||
processed while creating a sound fragment.
|
||||
*/
|
||||
class RegWriteQueue
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Create a new queue instance with the specified initial
|
||||
capacity. If the queue ever reaches its capacity then it will
|
||||
automatically increase its size.
|
||||
*/
|
||||
RegWriteQueue(uInt32 capacity = 512);
|
||||
|
||||
public:
|
||||
/**
|
||||
Clear any items stored in the queue.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
Dequeue the first object in the queue.
|
||||
*/
|
||||
void dequeue();
|
||||
|
||||
/**
|
||||
Return the duration of all the items in the queue.
|
||||
*/
|
||||
double duration() const;
|
||||
|
||||
/**
|
||||
Enqueue the specified object.
|
||||
*/
|
||||
void enqueue(uInt16 addr, uInt8 value, double delta);
|
||||
|
||||
/**
|
||||
Return the item at the front on the queue.
|
||||
|
||||
@return The item at the front of the queue.
|
||||
*/
|
||||
RegWrite& front() const;
|
||||
|
||||
/**
|
||||
Answers the number of items currently in the queue.
|
||||
|
||||
@return The number of items in the queue.
|
||||
*/
|
||||
uInt32 size() const;
|
||||
|
||||
private:
|
||||
// Increase the size of the queue
|
||||
void grow();
|
||||
|
||||
private:
|
||||
unique_ptr<RegWrite[]> myBuffer;
|
||||
uInt32 myCapacity;
|
||||
uInt32 mySize;
|
||||
uInt32 myHead;
|
||||
uInt32 myTail;
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
RegWriteQueue(const RegWriteQueue&) = delete;
|
||||
RegWriteQueue(RegWriteQueue&&) = delete;
|
||||
RegWriteQueue& operator=(const RegWriteQueue&) = delete;
|
||||
RegWriteQueue& operator=(RegWriteQueue&&) = delete;
|
||||
};
|
||||
void processFragment(float* stream, uInt32 length);
|
||||
|
||||
private:
|
||||
// TIASound emulation object
|
||||
TIASound myTIASound;
|
||||
|
||||
// Indicates if the sound subsystem is to be initialized
|
||||
bool myIsEnabled;
|
||||
void initResampler();
|
||||
|
||||
private:
|
||||
// Indicates if the sound device was successfully initialized
|
||||
bool myIsInitializedFlag;
|
||||
|
||||
// Indicates the cycle when a sound register was last set
|
||||
uInt64 myLastRegisterSetCycle;
|
||||
|
||||
// Indicates the number of channels (mono or stereo)
|
||||
uInt32 myNumChannels;
|
||||
|
||||
// Log base 2 of the selected fragment size
|
||||
double myFragmentSizeLogBase2;
|
||||
|
||||
// The myFragmentSizeLogBase2 variable is used in only two places,
|
||||
// both of which involve an expensive division in the sound
|
||||
// processing callback
|
||||
// These are pre-computed to speed up the callback as much as possible
|
||||
double myFragmentSizeLogDiv1, myFragmentSizeLogDiv2;
|
||||
|
||||
// Indicates if the sound is currently muted
|
||||
bool myIsMuted;
|
||||
|
||||
// Current volume as a percentage (0 - 100)
|
||||
uInt32 myVolume;
|
||||
float myVolumeFactor;
|
||||
|
||||
// Audio specification structure
|
||||
SDL_AudioSpec myHardwareSpec;
|
||||
|
||||
// Queue of TIA register writes
|
||||
RegWriteQueue myRegWriteQueue;
|
||||
shared_ptr<AudioQueue> myAudioQueue;
|
||||
|
||||
EmulationTiming* myEmulationTiming;
|
||||
|
||||
Int16* myCurrentFragment;
|
||||
bool myUnderrun;
|
||||
|
||||
unique_ptr<Resampler> myResampler;
|
||||
|
||||
AudioSettings& myAudioSettings;
|
||||
|
||||
private:
|
||||
// Callback function invoked by the SDL Audio library when it needs data
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#ifndef VERSION_HXX
|
||||
#define VERSION_HXX
|
||||
|
||||
#define STELLA_VERSION "5.2_pre"
|
||||
#define STELLA_VERSION "5.2_soundtest-1"
|
||||
#define STELLA_BUILD "4138"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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/SoundSDL2.o \
|
||||
src/common/StateManager.o \
|
||||
src/common/ZipHandler.o
|
||||
src/common/ZipHandler.o \
|
||||
src/common/AudioQueue.o \
|
||||
src/common/AudioSettings.o
|
||||
|
||||
MODULE_DIRS += \
|
||||
src/common
|
||||
|
|
|
@ -513,7 +513,7 @@ void Debugger::nextFrame(int frames)
|
|||
unlockSystem();
|
||||
while(frames)
|
||||
{
|
||||
myOSystem.console().tia().update();
|
||||
myOSystem.console().tia().update(myOSystem.console().emulationTiming().maxCyclesPerTimeslice());
|
||||
--frames;
|
||||
}
|
||||
lockSystem();
|
||||
|
|
|
@ -168,6 +168,8 @@ void TiaOutputWidget::drawWidget(bool hilite)
|
|||
uInt32 scanx, scany, scanoffset;
|
||||
bool visible = instance().console().tia().electronBeamPos(scanx, scany);
|
||||
scanoffset = width * scany + scanx;
|
||||
uInt8* tiaOutputBuffer = instance().console().tia().outputBuffer();
|
||||
TIASurface& tiaSurface(instance().frameBuffer().tiaSurface());
|
||||
|
||||
for(uInt32 y = 0, i = 0; y < height; ++y)
|
||||
{
|
||||
|
@ -175,7 +177,7 @@ void TiaOutputWidget::drawWidget(bool hilite)
|
|||
for(uInt32 x = 0; x < width; ++x, ++i)
|
||||
{
|
||||
uInt8 shift = i >= scanoffset ? 1 : 0;
|
||||
uInt32 pixel = instance().frameBuffer().tiaSurface().pixel(i, shift);
|
||||
uInt32 pixel = tiaSurface.mapIndexedPixel(tiaOutputBuffer[i], shift);
|
||||
*line_ptr++ = pixel;
|
||||
*line_ptr++ = pixel;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@
|
|||
#include "Version.hxx"
|
||||
#include "TIAConstants.hxx"
|
||||
#include "FrameLayout.hxx"
|
||||
#include "AudioQueue.hxx"
|
||||
#include "AudioSettings.hxx"
|
||||
#include "frame-manager/FrameManager.hxx"
|
||||
#include "frame-manager/FrameLayoutDetector.hxx"
|
||||
#include "frame-manager/YStartDetector.hxx"
|
||||
|
@ -75,17 +77,17 @@ namespace {
|
|||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
||||
const Properties& props)
|
||||
const Properties& props, AudioSettings& audioSettings)
|
||||
: myOSystem(osystem),
|
||||
myEvent(osystem.eventHandler().event()),
|
||||
myProperties(props),
|
||||
myCart(std::move(cart)),
|
||||
myDisplayFormat(""), // Unknown TV format @ start
|
||||
myFramerate(0.0), // Unknown framerate @ start
|
||||
myCurrentFormat(0), // Unknown format @ start,
|
||||
myAutodetectedYstart(0),
|
||||
myUserPaletteDefined(false),
|
||||
myConsoleTiming(ConsoleTiming::ntsc)
|
||||
myConsoleTiming(ConsoleTiming::ntsc),
|
||||
myAudioSettings(audioSettings)
|
||||
{
|
||||
// Load user-defined palette for this ROM
|
||||
loadUserPalette();
|
||||
|
@ -93,7 +95,7 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
|||
// Create subsystems for the console
|
||||
my6502 = make_unique<M6502>(myOSystem.settings());
|
||||
myRiot = make_unique<M6532>(*this, myOSystem.settings());
|
||||
myTIA = make_unique<TIA>(*this, myOSystem.sound(), myOSystem.settings());
|
||||
myTIA = make_unique<TIA>(*this, myOSystem.settings());
|
||||
myFrameManager = make_unique<FrameManager>();
|
||||
mySwitches = make_unique<Switches>(myEvent, myProperties, myOSystem.settings());
|
||||
|
||||
|
@ -146,7 +148,6 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
|||
// Note that this can be overridden if a format is forced
|
||||
// For example, if a PAL ROM is forced to be NTSC, it will use NTSC-like
|
||||
// properties (60Hz, 262 scanlines, etc), but likely result in flicker
|
||||
// The TIA will self-adjust the framerate if necessary
|
||||
setTIAProperties();
|
||||
if(myDisplayFormat == "NTSC")
|
||||
{
|
||||
|
@ -205,6 +206,10 @@ Console::~Console()
|
|||
// Some smart controllers need to be informed that the console is going away
|
||||
myLeftControl->close();
|
||||
myRightControl->close();
|
||||
|
||||
// Close audio to prevent invalid access to myConsoleTiming from the audio
|
||||
// callback
|
||||
myOSystem.sound().close();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -381,6 +386,7 @@ void Console::setFormat(int format)
|
|||
setTIAProperties();
|
||||
myTIA->frameReset();
|
||||
initializeVideo(); // takes care of refreshing the screen
|
||||
initializeAudio(); // ensure that audio synthesis is set up to match emulation speed
|
||||
}
|
||||
|
||||
myOSystem.frameBuffer().showMessage(message);
|
||||
|
@ -569,37 +575,29 @@ FBInitStatus Console::initializeVideo(bool full)
|
|||
}
|
||||
setPalette(myOSystem.settings().getString("palette"));
|
||||
|
||||
// Set the correct framerate based on the format of the ROM
|
||||
// This can be overridden by changing the framerate in the
|
||||
// VideoDialog box or on the commandline, but it can't be saved
|
||||
// (ie, framerate is now determined based on number of scanlines).
|
||||
int framerate = myOSystem.settings().getInt("framerate");
|
||||
if(framerate > 0) myFramerate = float(framerate);
|
||||
myOSystem.setFramerate(myFramerate);
|
||||
|
||||
// Make sure auto-frame calculation is only enabled when necessary
|
||||
myTIA->enableAutoFrame(framerate <= 0);
|
||||
|
||||
return fbstatus;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Console::initializeAudio()
|
||||
{
|
||||
// Initialize the sound interface.
|
||||
// The # of channels can be overridden in the AudioDialog box or on
|
||||
// the commandline, but it can't be saved.
|
||||
int framerate = myOSystem.settings().getInt("framerate");
|
||||
if(framerate > 0) myFramerate = float(framerate);
|
||||
const string& sound = myProperties.get(Cartridge_Sound);
|
||||
|
||||
myOSystem.sound().close();
|
||||
myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1);
|
||||
myOSystem.sound().setFrameRate(myFramerate);
|
||||
myOSystem.sound().open();
|
||||
|
||||
// Make sure auto-frame calculation is only enabled when necessary
|
||||
myTIA->enableAutoFrame(framerate <= 0);
|
||||
myEmulationTiming
|
||||
.updatePlaybackRate(myOSystem.sound().getSampleRate())
|
||||
.updatePlaybackPeriod(myOSystem.sound().getFragmentSize())
|
||||
.updateAudioQueueExtraFragments(myAudioSettings.bufferSize())
|
||||
.updateAudioQueueHeadroom(myAudioSettings.headroom())
|
||||
.updateSpeedFactor(myOSystem.settings().getFloat("speed"));
|
||||
|
||||
(cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush();
|
||||
(cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush();
|
||||
(cout << "prebuffer fragment count: " << myEmulationTiming.prebufferFragmentCount() << std::endl).flush();
|
||||
|
||||
createAudioQueue();
|
||||
myTIA->setAudioQueue(myAudioQueue);
|
||||
|
||||
myOSystem.sound().open(myAudioQueue, &myEmulationTiming);
|
||||
}
|
||||
|
||||
/* Original frying research and code by Fred Quimby.
|
||||
|
@ -729,16 +727,11 @@ void Console::setTIAProperties()
|
|||
myDisplayFormat == "SECAM60")
|
||||
{
|
||||
// Assume we've got ~262 scanlines (NTSC-like format)
|
||||
myFramerate = 60.0;
|
||||
myConsoleInfo.InitialFrameRate = "60";
|
||||
myTIA->setLayout(FrameLayout::ntsc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume we've got ~312 scanlines (PAL-like format)
|
||||
myFramerate = 50.0;
|
||||
myConsoleInfo.InitialFrameRate = "50";
|
||||
|
||||
// PAL ROMs normally need at least 250 lines
|
||||
if (height != 0) height = std::max(height, 250u);
|
||||
|
||||
|
@ -747,6 +740,18 @@ void Console::setTIAProperties()
|
|||
|
||||
myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart);
|
||||
myTIA->setHeight(height);
|
||||
|
||||
myEmulationTiming.updateFrameLayout(myTIA->frameLayout());
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Console::createAudioQueue()
|
||||
{
|
||||
myAudioQueue = make_shared<AudioQueue>(
|
||||
myEmulationTiming.audioFragmentSize(),
|
||||
myEmulationTiming.audioQueueCapacity(),
|
||||
myProperties.get(Cartridge_Sound) == "STEREO"
|
||||
);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -967,11 +972,9 @@ void Console::generateColorLossPalette()
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Console::setFramerate(float framerate)
|
||||
float Console::getFramerate() const
|
||||
{
|
||||
myFramerate = framerate;
|
||||
myOSystem.setFramerate(framerate);
|
||||
myOSystem.sound().setFrameRate(framerate);
|
||||
return myTIA->frameBufferFrameRate();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1103,7 +1106,7 @@ uInt32 Console::ourPALPalette[256] = {
|
|||
0x001759, 0, 0x00247c, 0, 0x1d469e, 0, 0x3f68c0, 0, // 288 b
|
||||
0x618ae2, 0, 0x83acff, 0, 0xa5ceff, 0, 0xc7f0ff, 0,
|
||||
0x12006d, 0, 0x34038f, 0, 0x5625b1, 0, 0x7847d3, 0, // 2a0 c
|
||||
0x9a69f5, 0, 0xb48cff, 0, 0xc9adff, 0, 0xe1d1ff, 0, // was ..0xbc8bff, 0xdeadff, 0xffcfff,
|
||||
0x9a69f5, 0, 0xb48cff, 0, 0xc9adff, 0, 0xe1d1ff, 0, // was ..0xbc8bff, 0xdeadff, 0xffcfff,
|
||||
0x000070, 0, 0x161292, 0, 0x3834b4, 0, 0x5a56d6, 0, // 2b8 d
|
||||
0x7c78f8, 0, 0x9e9aff, 0, 0xc0bcff, 0, 0xe2deff, 0,
|
||||
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2d0 e
|
||||
|
|
|
@ -27,6 +27,8 @@ class M6532;
|
|||
class Cartridge;
|
||||
class CompuMate;
|
||||
class Debugger;
|
||||
class AudioQueue;
|
||||
class AudioSettings;
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "Control.hxx"
|
||||
|
@ -36,6 +38,7 @@ class Debugger;
|
|||
#include "Serializable.hxx"
|
||||
#include "EventHandlerConstants.hxx"
|
||||
#include "NTSCFilter.hxx"
|
||||
#include "EmulationTiming.hxx"
|
||||
#include "frame-manager/AbstractFrameManager.hxx"
|
||||
|
||||
/**
|
||||
|
@ -49,7 +52,6 @@ struct ConsoleInfo
|
|||
string Control0;
|
||||
string Control1;
|
||||
string DisplayFormat;
|
||||
string InitialFrameRate;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -79,7 +81,7 @@ class Console : public Serializable
|
|||
@param props The properties for the cartridge
|
||||
*/
|
||||
Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
|
||||
const Properties& props);
|
||||
const Properties& props, AudioSettings& audioSettings);
|
||||
|
||||
/**
|
||||
Destructor
|
||||
|
@ -190,6 +192,11 @@ class Console : public Serializable
|
|||
*/
|
||||
void stateChanged(EventHandlerState state);
|
||||
|
||||
/**
|
||||
Retrieve emulation timing provider.
|
||||
*/
|
||||
EmulationTiming& emulationTiming() { return myEmulationTiming; }
|
||||
|
||||
public:
|
||||
/**
|
||||
Toggle between NTSC/PAL/SECAM (and variants) display format.
|
||||
|
@ -270,16 +277,9 @@ class Console : public Serializable
|
|||
void changeHeight(int direction);
|
||||
|
||||
/**
|
||||
Sets the framerate of the console, which in turn communicates
|
||||
this to all applicable subsystems.
|
||||
Returns the current framerate.
|
||||
*/
|
||||
void setFramerate(float framerate);
|
||||
|
||||
/**
|
||||
Returns the framerate based on a number of factors
|
||||
(whether 'framerate' is set, what display format is in use, etc)
|
||||
*/
|
||||
float getFramerate() const { return myFramerate; }
|
||||
float getFramerate() const;
|
||||
|
||||
/**
|
||||
Toggles the TIA bit specified in the method name.
|
||||
|
@ -330,6 +330,11 @@ class Console : public Serializable
|
|||
*/
|
||||
void setTIAProperties();
|
||||
|
||||
/**
|
||||
Create the audio queue
|
||||
*/
|
||||
void createAudioQueue();
|
||||
|
||||
/**
|
||||
Adds the left and right controllers to the console.
|
||||
*/
|
||||
|
@ -389,6 +394,9 @@ class Console : public Serializable
|
|||
// The frame manager instance that is used during emulation.
|
||||
unique_ptr<AbstractFrameManager> myFrameManager;
|
||||
|
||||
// The audio fragment queue that connects TIA and audio driver
|
||||
shared_ptr<AudioQueue> myAudioQueue;
|
||||
|
||||
// Pointer to the Cartridge (the debugger needs it)
|
||||
unique_ptr<Cartridge> myCart;
|
||||
|
||||
|
@ -404,9 +412,6 @@ class Console : public Serializable
|
|||
// The currently defined display format (NTSC/PAL/SECAM)
|
||||
string myDisplayFormat;
|
||||
|
||||
// The currently defined display framerate
|
||||
float myFramerate;
|
||||
|
||||
// Display format currently in use
|
||||
uInt32 myCurrentFormat;
|
||||
|
||||
|
@ -423,6 +428,13 @@ class Console : public Serializable
|
|||
// Contains timing information for this console
|
||||
ConsoleTiming myConsoleTiming;
|
||||
|
||||
// Emulation timing provider. This ties together the timing of the core emulation loop
|
||||
// and the parameters that govern audio synthesis
|
||||
EmulationTiming myEmulationTiming;
|
||||
|
||||
// The audio settings
|
||||
AudioSettings& myAudioSettings;
|
||||
|
||||
// Table of RGB values for NTSC, PAL and SECAM
|
||||
static uInt32 ourNTSCPalette[256];
|
||||
static uInt32 ourPALPalette[256];
|
||||
|
|
|
@ -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),
|
||||
myStatsEnabled(false),
|
||||
myLastScanlines(0),
|
||||
myLastFrameRate(60),
|
||||
myGrabMouse(false),
|
||||
myCurrentModeList(nullptr),
|
||||
myTotalTime(0),
|
||||
myTotalFrames(0)
|
||||
{
|
||||
}
|
||||
myCurrentModeList(nullptr)
|
||||
{}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
FrameBuffer::~FrameBuffer()
|
||||
|
@ -272,29 +268,8 @@ void FrameBuffer::update()
|
|||
switch(myOSystem.eventHandler().state())
|
||||
{
|
||||
case EventHandlerState::EMULATION:
|
||||
{
|
||||
// Run the console for one frame
|
||||
// Note that the debugger can cause a breakpoint to occur, which changes
|
||||
// the EventHandler state 'behind our back' - we need to check for that
|
||||
myOSystem.console().tia().update();
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break;
|
||||
#endif
|
||||
if(myOSystem.eventHandler().frying())
|
||||
myOSystem.console().fry();
|
||||
|
||||
// And update the screen
|
||||
myTIASurface->render();
|
||||
|
||||
// Show frame statistics
|
||||
if(myStatsMsg.enabled)
|
||||
drawFrameStats();
|
||||
else
|
||||
myLastFrameRate = myOSystem.console().getFramerate();
|
||||
myLastScanlines = myOSystem.console().tia().scanlinesLastFrame();
|
||||
myPausedCount = 0;
|
||||
break; // EventHandlerState::EMULATION
|
||||
}
|
||||
// Do nothing; emulation mode is handled separately (see below)
|
||||
break;
|
||||
|
||||
case EventHandlerState::PAUSE:
|
||||
{
|
||||
|
@ -356,6 +331,30 @@ void FrameBuffer::update()
|
|||
postFrameUpdate();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void FrameBuffer::updateInEmulationMode()
|
||||
{
|
||||
// Determine which mode we are in (from the EventHandler)
|
||||
// Take care of S_EMULATE mode here, otherwise let the GUI
|
||||
// figure out what to draw
|
||||
|
||||
myTIASurface->render();
|
||||
|
||||
// Show frame statistics
|
||||
if(myStatsMsg.enabled)
|
||||
drawFrameStats();
|
||||
|
||||
myLastScanlines = myOSystem.console().tia().frameBufferScanlinesLastFrame();
|
||||
myPausedCount = 0;
|
||||
|
||||
// Draw any pending messages
|
||||
if(myMsg.enabled)
|
||||
drawMessage();
|
||||
|
||||
// Do any post-frame stuff
|
||||
postFrameUpdate();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
||||
bool force)
|
||||
|
@ -367,6 +366,7 @@ void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
|||
// Precompute the message coordinates
|
||||
myMsg.text = message;
|
||||
myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds
|
||||
if(myMsg.counter == 0) myMsg.counter = 60;
|
||||
myMsg.color = kBtnTextColor;
|
||||
|
||||
myMsg.w = font().getStringWidth(myMsg.text) + 10;
|
||||
|
@ -389,9 +389,9 @@ void FrameBuffer::drawFrameStats()
|
|||
myStatsMsg.surface->invalidate();
|
||||
|
||||
// draw scanlines
|
||||
color = myOSystem.console().tia().scanlinesLastFrame() != myLastScanlines ?
|
||||
color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ?
|
||||
uInt32(kDbgColorRed) : myStatsMsg.color;
|
||||
std::snprintf(msg, 30, "%3u", myOSystem.console().tia().scanlinesLastFrame());
|
||||
std::snprintf(msg, 30, "%3u", myOSystem.console().tia().frameBufferScanlinesLastFrame());
|
||||
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
|
||||
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
|
||||
xPos += font().getStringWidth(msg);
|
||||
|
@ -402,23 +402,10 @@ void FrameBuffer::drawFrameStats()
|
|||
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
||||
xPos += font().getStringWidth(msg);
|
||||
|
||||
// draw the effective framerate
|
||||
float frameRate;
|
||||
const TimingInfo& ti = myOSystem.timingInfo();
|
||||
if(ti.totalFrames - myTotalFrames >= myLastFrameRate)
|
||||
{
|
||||
frameRate = 1000000.0 * (ti.totalFrames - myTotalFrames) / (ti.totalTime - myTotalTime);
|
||||
if(frameRate > myOSystem.console().getFramerate() + 1)
|
||||
frameRate = 1;
|
||||
myTotalFrames = ti.totalFrames;
|
||||
myTotalTime = ti.totalTime;
|
||||
}
|
||||
else
|
||||
frameRate = myLastFrameRate;
|
||||
std::snprintf(msg, 30, " @ %5.2ffps", frameRate);
|
||||
std::snprintf(msg, 30, " @ %5.2ffps", myOSystem.console().getFramerate());
|
||||
|
||||
myStatsMsg.surface->drawString(font(), msg, xPos, YPOS,
|
||||
myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor);
|
||||
myLastFrameRate = frameRate;
|
||||
|
||||
// draw bankswitching type
|
||||
string bsinfo = info.BankSwitch +
|
||||
|
@ -751,8 +738,7 @@ void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight)
|
|||
myDesktopSize.w, myDesktopSize.h);
|
||||
|
||||
// Aspect ratio
|
||||
bool ntsc = myOSystem.console().about().InitialFrameRate == "60";
|
||||
uInt32 aspect = myOSystem.settings().getInt(ntsc ?
|
||||
uInt32 aspect = myOSystem.settings().getInt(myOSystem.console().timing() == ConsoleTiming::ntsc ?
|
||||
"tia.aspectn" : "tia.aspectp");
|
||||
|
||||
// Figure our the smallest zoom level we can use
|
||||
|
|
|
@ -113,10 +113,16 @@ class FrameBuffer
|
|||
|
||||
/**
|
||||
Updates the display, which depending on the current mode could mean
|
||||
drawing the TIA, any pending menus, etc.
|
||||
drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles
|
||||
spent during emulation, or -1 if not applicable.
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
There is a dedicated update method for emulation mode.
|
||||
*/
|
||||
void updateInEmulationMode();
|
||||
|
||||
/**
|
||||
Shows a message onscreen.
|
||||
|
||||
|
@ -522,7 +528,6 @@ class FrameBuffer
|
|||
Message myStatsMsg;
|
||||
bool myStatsEnabled;
|
||||
uInt32 myLastScanlines;
|
||||
float myLastFrameRate;
|
||||
|
||||
bool myGrabMouse;
|
||||
|
||||
|
@ -540,9 +545,6 @@ class FrameBuffer
|
|||
// Holds UI palette data (standard and classic colours)
|
||||
static uInt32 ourGUIColors[3][kNumColors-256];
|
||||
|
||||
uInt64 myTotalTime;
|
||||
uInt64 myTotalFrames;
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
FrameBuffer() = delete;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
|
||||
#include "System.hxx"
|
||||
#include "M6502.hxx"
|
||||
#include "DispatchResult.hxx"
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
M6502::M6502(const Settings& settings)
|
||||
|
@ -208,9 +209,9 @@ inline void M6502::handleHalt()
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool M6502::execute(uInt32 number)
|
||||
void M6502::execute(uInt32 number, DispatchResult& result)
|
||||
{
|
||||
const bool status = _execute(number);
|
||||
_execute(number, result);
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
// Debugger hack: this ensures that stepping a "STA WSYNC" will actually end at the
|
||||
|
@ -218,18 +219,27 @@ bool M6502::execute(uInt32 number)
|
|||
// the halt to take effect). This is safe because as we know that the next cycle will be a read
|
||||
// cycle anyway.
|
||||
handleHalt();
|
||||
|
||||
// Make sure that the hardware state matches the current system clock. This is necessary
|
||||
// to maintain a consistent state for the debugger after stepping.
|
||||
mySystem->tia().updateEmulation();
|
||||
mySystem->m6532().updateEmulation();
|
||||
#endif
|
||||
|
||||
return status;
|
||||
// Make sure that the hardware state matches the current system clock. This is necessary
|
||||
// to maintain a consistent state for the debugger after stepping and to make sure
|
||||
// that audio samples are generated for the whole timeslice.
|
||||
mySystem->tia().updateEmulation();
|
||||
mySystem->m6532().updateEmulation();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
inline bool M6502::_execute(uInt32 number)
|
||||
bool M6502::execute(uInt32 number)
|
||||
{
|
||||
DispatchResult result;
|
||||
|
||||
_execute(number, result);
|
||||
|
||||
return result.isSuccess();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
inline void M6502::_execute(uInt32 cycles, DispatchResult& result)
|
||||
{
|
||||
// Clear all of the execution status bits except for the fatal error bit
|
||||
myExecutionStatus &= FatalErrorBit;
|
||||
|
@ -239,32 +249,43 @@ inline bool M6502::_execute(uInt32 number)
|
|||
M6532& riot = mySystem->m6532();
|
||||
#endif
|
||||
|
||||
uInt64 previousCycles = mySystem->cycles();
|
||||
uInt32 currentCycles = 0;
|
||||
|
||||
// Loop until execution is stopped or a fatal error occurs
|
||||
for(;;)
|
||||
{
|
||||
for(; !myExecutionStatus && (number != 0); --number)
|
||||
while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU)
|
||||
{
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
|
||||
{
|
||||
bool read = myJustHitReadTrapFlag;
|
||||
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
|
||||
// Don't break if we haven't actually executed anything yet
|
||||
if (currentCycles > 0) {
|
||||
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
|
||||
{
|
||||
bool read = myJustHitReadTrapFlag;
|
||||
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
|
||||
|
||||
if (startDebugger(myHitTrapInfo.message, myHitTrapInfo.address, read)) return true;
|
||||
result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read);
|
||||
return;
|
||||
}
|
||||
|
||||
if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) {
|
||||
result.setDebugger(currentCycles, "BP: ", PC);
|
||||
return;
|
||||
}
|
||||
|
||||
int cond = evalCondBreaks();
|
||||
if(cond > -1)
|
||||
{
|
||||
stringstream msg;
|
||||
msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond];
|
||||
|
||||
result.setDebugger(currentCycles, msg.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC) && startDebugger("BP: ", PC))
|
||||
return true;
|
||||
|
||||
int cond = evalCondBreaks();
|
||||
if(cond > -1)
|
||||
{
|
||||
stringstream msg;
|
||||
msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond];
|
||||
if (startDebugger(msg.str())) return true;
|
||||
}
|
||||
|
||||
cond = evalCondSaveStates();
|
||||
int cond = evalCondSaveStates();
|
||||
if(cond > -1)
|
||||
{
|
||||
stringstream msg;
|
||||
|
@ -294,6 +315,8 @@ inline bool M6502::_execute(uInt32 number)
|
|||
myExecutionStatus |= FatalErrorBit;
|
||||
}
|
||||
|
||||
currentCycles = (mySystem->cycles() - previousCycles);
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
if(myStepStateByInstruction)
|
||||
{
|
||||
|
@ -318,21 +341,24 @@ inline bool M6502::_execute(uInt32 number)
|
|||
if(myExecutionStatus & StopExecutionBit)
|
||||
{
|
||||
// Yes, so answer that everything finished fine
|
||||
return true;
|
||||
result.setOk(currentCycles);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if a fatal error has occured
|
||||
if(myExecutionStatus & FatalErrorBit)
|
||||
{
|
||||
// Yes, so answer that something when wrong
|
||||
return false;
|
||||
result.setFatal(currentCycles + icycles);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we've executed the specified number of instructions
|
||||
if(number == 0)
|
||||
if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU)
|
||||
{
|
||||
// Yes, so answer that everything finished fine
|
||||
return true;
|
||||
result.setOk(currentCycles);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -606,15 +632,4 @@ void M6502::updateStepStateByInstruction()
|
|||
myTrapConds.size();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool M6502::startDebugger(const string& message, int address, bool read)
|
||||
{
|
||||
handleHalt();
|
||||
|
||||
mySystem->tia().updateEmulation();
|
||||
mySystem->m6532().updateEmulation();
|
||||
|
||||
return myDebugger->start(message, address, read);
|
||||
}
|
||||
|
||||
#endif // DEBUGGER_SUPPORT
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
class Settings;
|
||||
class System;
|
||||
class DispatchResult;
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
class Debugger;
|
||||
|
@ -110,10 +111,14 @@ class M6502 : public Serializable
|
|||
is executed, someone stops execution, or an error occurs. Answers
|
||||
true iff execution stops normally.
|
||||
|
||||
@param number Indicates the number of instructions to execute
|
||||
@return true iff execution stops normally
|
||||
@param cycles Indicates the number of cycles to execute. Not that the actual
|
||||
granularity of the CPU is instructions, so this is only accurate up to
|
||||
a couple of cycles
|
||||
@param result A DispatchResult object that will transport the result
|
||||
*/
|
||||
bool execute(uInt32 number);
|
||||
void execute(uInt32 cycles, DispatchResult& result);
|
||||
|
||||
bool execute(uInt32 cycles);
|
||||
|
||||
/**
|
||||
Tell the processor to stop executing instructions. Invoking this
|
||||
|
@ -318,7 +323,7 @@ class M6502 : public Serializable
|
|||
This is the actual dispatch function that does the grunt work. M6502::execute
|
||||
wraps it and makes sure that any pending halt is processed before returning.
|
||||
*/
|
||||
bool _execute(uInt32 number);
|
||||
void _execute(uInt32 cycles, DispatchResult& result);
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
/**
|
||||
|
@ -326,12 +331,6 @@ class M6502 : public Serializable
|
|||
with the CPU and update the flag accordingly.
|
||||
*/
|
||||
void updateStepStateByInstruction();
|
||||
|
||||
/**
|
||||
Make sure that the current hardware state is up to date (TIA & RIOT) and dispatch
|
||||
debugger.
|
||||
*/
|
||||
bool startDebugger(const string& message = "", int address = -1, bool read = true);
|
||||
#endif // DEBUGGER_SUPPORT
|
||||
|
||||
private:
|
||||
|
|
|
@ -17,11 +17,6 @@
|
|||
|
||||
#include <cassert>
|
||||
|
||||
#include <ctime>
|
||||
#ifdef HAVE_GETTIMEOFDAY
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include "bspf.hxx"
|
||||
|
||||
#include "MediaFactory.hxx"
|
||||
|
@ -35,6 +30,8 @@
|
|||
#include "CheatManager.hxx"
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "FSNode.hxx"
|
||||
#include "MD5.hxx"
|
||||
#include "Cart.hxx"
|
||||
|
@ -55,17 +52,19 @@
|
|||
#include "SerialPort.hxx"
|
||||
#include "StateManager.hxx"
|
||||
#include "Version.hxx"
|
||||
#include "TIA.hxx"
|
||||
#include "DispatchResult.hxx"
|
||||
#include "EmulationWorker.hxx"
|
||||
|
||||
#include "OSystem.hxx"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
OSystem::OSystem()
|
||||
: myLauncherUsed(false),
|
||||
myQuitLoop(false)
|
||||
{
|
||||
// Calculate startup time
|
||||
myMillisAtStart = uInt32(time(nullptr) * 1000);
|
||||
|
||||
// Get built-in features
|
||||
#ifdef SOUND_SUPPORT
|
||||
myFeatures += "Sound ";
|
||||
|
@ -91,6 +90,7 @@ OSystem::OSystem()
|
|||
myBuildInfo = info.str();
|
||||
|
||||
mySettings = MediaFactory::createSettings(*this);
|
||||
myAudioSettings = AudioSettings(mySettings.get());
|
||||
myRandom = make_unique<Random>(*this);
|
||||
}
|
||||
|
||||
|
@ -283,16 +283,6 @@ void OSystem::setConfigFile(const string& file)
|
|||
myConfigFile = FilesystemNode(file).getPath();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void OSystem::setFramerate(float framerate)
|
||||
{
|
||||
if(framerate > 0.0)
|
||||
{
|
||||
myDisplayFrameRate = framerate;
|
||||
myTimePerFrame = uInt32(1000000.0 / myDisplayFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
FBInitStatus OSystem::createFrameBuffer()
|
||||
{
|
||||
|
@ -332,9 +322,9 @@ FBInitStatus OSystem::createFrameBuffer()
|
|||
void OSystem::createSound()
|
||||
{
|
||||
if(!mySound)
|
||||
mySound = MediaFactory::createAudio(*this);
|
||||
mySound = MediaFactory::createAudio(*this, myAudioSettings);
|
||||
#ifndef SOUND_SUPPORT
|
||||
mySettings->setValue("sound", false);
|
||||
myAudioSettings.setEnabled(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -408,9 +398,6 @@ string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
|
|||
<< getROMInfo(*myConsole) << endl;
|
||||
logMessage(buf.str(), 1);
|
||||
|
||||
// Update the timing info for a new console run
|
||||
resetLoopTiming();
|
||||
|
||||
myFrameBuffer->setCursorState();
|
||||
|
||||
// Also check if certain virtual buttons should be held down
|
||||
|
@ -450,8 +437,6 @@ bool OSystem::createLauncher(const string& startdir)
|
|||
myLauncher->reStack();
|
||||
myFrameBuffer->setCursorState();
|
||||
|
||||
setFramerate(30);
|
||||
resetLoopTiming();
|
||||
status = true;
|
||||
}
|
||||
else
|
||||
|
@ -565,7 +550,7 @@ unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string&
|
|||
|
||||
// Finally, create the cart with the correct properties
|
||||
if(cart)
|
||||
console = make_unique<Console>(*this, cart, props);
|
||||
console = make_unique<Console>(*this, cart, props, myAudioSettings);
|
||||
}
|
||||
|
||||
return console;
|
||||
|
@ -627,15 +612,6 @@ string OSystem::getROMInfo(const Console& console)
|
|||
return buf.str();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void OSystem::resetLoopTiming()
|
||||
{
|
||||
myTimingInfo.start = myTimingInfo.virt = getTicks();
|
||||
myTimingInfo.current = 0;
|
||||
myTimingInfo.totalTime = 0;
|
||||
myTimingInfo.totalFrames = 0;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void OSystem::validatePath(string& path, const string& setting,
|
||||
const string& defaultpath)
|
||||
|
@ -653,68 +629,105 @@ void OSystem::validatePath(string& path, const string& setting,
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt64 OSystem::getTicks() const
|
||||
{
|
||||
#ifdef HAVE_GETTIMEOFDAY
|
||||
// Gettimeofday natively refers to the UNIX epoch (a set time in the past)
|
||||
timeval now;
|
||||
gettimeofday(&now, nullptr);
|
||||
return duration_cast<duration<uInt64, std::ratio<1, 1000000> > >(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
return uInt64(now.tv_sec) * 1000000 + now.tv_usec;
|
||||
#else
|
||||
// We use SDL_GetTicks, but add in the time when the application was
|
||||
// initialized. This is necessary, since SDL_GetTicks only measures how
|
||||
// long SDL has been running, which can be the same between multiple runs
|
||||
// of the application.
|
||||
return uInt64(SDL_GetTicks() + myMillisAtStart) * 1000;
|
||||
#endif
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
float OSystem::frameRate() const
|
||||
{
|
||||
return myConsole ? myConsole->getFramerate() : 0;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
double OSystem::dispatchEmulation(EmulationWorker& emulationWorker)
|
||||
{
|
||||
if (!myConsole) return 0.;
|
||||
|
||||
TIA& tia(myConsole->tia());
|
||||
EmulationTiming& timing(myConsole->emulationTiming());
|
||||
DispatchResult dispatchResult;
|
||||
|
||||
// Check whether we have a frame pending for rendering...
|
||||
bool framePending = tia.newFramePending();
|
||||
// ... and copy it to the frame buffer. It is important to do this before
|
||||
// the worker is started to avoid racing.
|
||||
if (framePending) tia.renderToFrameBuffer();
|
||||
|
||||
// Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time
|
||||
// and will run until we stop the worker.
|
||||
emulationWorker.start(
|
||||
timing.cyclesPerSecond(),
|
||||
timing.maxCyclesPerTimeslice(),
|
||||
timing.minCyclesPerTimeslice(),
|
||||
&dispatchResult,
|
||||
&tia
|
||||
);
|
||||
|
||||
// Render the frame. This may block, but emulation will continue to run on the worker, so the
|
||||
// audio pipeline is kept fed :)
|
||||
if (framePending) myFrameBuffer->updateInEmulationMode();
|
||||
|
||||
// Stop the worker and wait until it has finished
|
||||
uInt64 totalCycles = emulationWorker.stop();
|
||||
|
||||
// Break or trap? -> start debugger
|
||||
if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start();
|
||||
|
||||
// Handle frying
|
||||
if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
|
||||
myConsole->fry();
|
||||
|
||||
// Return the 6507 time used in seconds
|
||||
return static_cast<double>(totalCycles) / static_cast<double>(timing.cyclesPerSecond());
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void OSystem::mainLoop()
|
||||
{
|
||||
if(mySettings->getString("timing") == "sleep")
|
||||
// Sleep-based wait: good for CPU, bad for graphical sync
|
||||
bool busyWait = mySettings->getString("timing") != "sleep";
|
||||
// 6507 time
|
||||
time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
|
||||
// The emulation worker
|
||||
EmulationWorker emulationWorker;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
// Sleep-based wait: good for CPU, bad for graphical sync
|
||||
for(;;)
|
||||
{
|
||||
myTimingInfo.start = getTicks();
|
||||
myEventHandler->poll(myTimingInfo.start);
|
||||
if(myQuitLoop) break; // Exit if the user wants to quit
|
||||
myEventHandler->poll(getTicks());
|
||||
if(myQuitLoop) break; // Exit if the user wants to quit
|
||||
|
||||
double timesliceSeconds;
|
||||
|
||||
if (myEventHandler->state() == EventHandlerState::EMULATION)
|
||||
// Dispatch emulation and render frame (if applicable)
|
||||
timesliceSeconds = dispatchEmulation(emulationWorker);
|
||||
else {
|
||||
// Render the GUI with 30 Hz in all other modes
|
||||
timesliceSeconds = 1. / 30.;
|
||||
myFrameBuffer->update();
|
||||
myTimingInfo.current = getTicks();
|
||||
myTimingInfo.virt += myTimePerFrame;
|
||||
|
||||
// Timestamps may periodically go out of sync, particularly on systems
|
||||
// that can have 'negative time' (ie, when the time seems to go backwards)
|
||||
// This normally results in having a very large delay time, so we check
|
||||
// for that and reset the timers when appropriate
|
||||
if((myTimingInfo.virt - myTimingInfo.current) > (myTimePerFrame << 1))
|
||||
{
|
||||
myTimingInfo.current = myTimingInfo.virt = getTicks();
|
||||
}
|
||||
|
||||
if(myTimingInfo.current < myTimingInfo.virt)
|
||||
SDL_Delay(uInt32(myTimingInfo.virt - myTimingInfo.current) / 1000);
|
||||
|
||||
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
|
||||
myTimingInfo.totalFrames++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Busy-wait: bad for CPU, good for graphical sync
|
||||
for(;;)
|
||||
{
|
||||
myTimingInfo.start = getTicks();
|
||||
myEventHandler->poll(myTimingInfo.start);
|
||||
if(myQuitLoop) break; // Exit if the user wants to quit
|
||||
myFrameBuffer->update();
|
||||
myTimingInfo.virt += myTimePerFrame;
|
||||
|
||||
while(getTicks() < myTimingInfo.virt)
|
||||
; // busy-wait
|
||||
duration<double> timeslice(timesliceSeconds);
|
||||
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
|
||||
time_point<high_resolution_clock> now = high_resolution_clock::now();
|
||||
|
||||
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
|
||||
myTimingInfo.totalFrames++;
|
||||
// We allow 6507 time to lag behind by one frame max
|
||||
double maxLag = myConsole
|
||||
? (
|
||||
static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
|
||||
static_cast<double>(myConsole->emulationTiming().cyclesPerSecond())
|
||||
)
|
||||
: 0;
|
||||
|
||||
if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
|
||||
// If 6507 time is lagging behind more than one frame we reset it to real time
|
||||
virtualTime = now;
|
||||
else if (virtualTime > now) {
|
||||
// Wait until we have caught up with 6507 time
|
||||
if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) {
|
||||
while (high_resolution_clock::now() < virtualTime);
|
||||
}
|
||||
else std::this_thread::sleep_until(virtualTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,19 +38,13 @@ class Settings;
|
|||
class Sound;
|
||||
class StateManager;
|
||||
class VideoDialog;
|
||||
class EmulationWorker;
|
||||
|
||||
#include "FSNode.hxx"
|
||||
#include "FrameBufferConstants.hxx"
|
||||
#include "EventHandlerConstants.hxx"
|
||||
#include "bspf.hxx"
|
||||
|
||||
struct TimingInfo {
|
||||
uInt64 start;
|
||||
uInt64 current;
|
||||
uInt64 virt;
|
||||
uInt64 totalTime;
|
||||
uInt64 totalFrames;
|
||||
};
|
||||
#include "AudioSettings.hxx"
|
||||
|
||||
/**
|
||||
This class provides an interface for accessing operating system specific
|
||||
|
@ -132,6 +126,11 @@ class OSystem
|
|||
Console& console() const { return *myConsole; }
|
||||
bool hasConsole() const;
|
||||
|
||||
/**
|
||||
Get the audio settings ovject.
|
||||
*/
|
||||
AudioSettings& audioSettings() { return myAudioSettings; }
|
||||
|
||||
/**
|
||||
Get the serial port of the system.
|
||||
|
||||
|
@ -213,26 +212,11 @@ class OSystem
|
|||
CheatManager& cheat() const { return *myCheatManager; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
Set the framerate for the video system. It's placed in this class since
|
||||
the mainLoop() method is defined here.
|
||||
|
||||
@param framerate The video framerate to use
|
||||
*/
|
||||
virtual void setFramerate(float framerate);
|
||||
|
||||
/**
|
||||
Set all config file paths for the OSystem.
|
||||
*/
|
||||
void setConfigPaths();
|
||||
|
||||
/**
|
||||
Get the current framerate for the video system.
|
||||
|
||||
@return The video framerate currently in use
|
||||
*/
|
||||
float frameRate() const { return myDisplayFrameRate; }
|
||||
|
||||
/**
|
||||
Return the default full/complete directory name for storing data.
|
||||
*/
|
||||
|
@ -383,12 +367,6 @@ class OSystem
|
|||
*/
|
||||
const string& logMessages() const { return myLogMessages; }
|
||||
|
||||
/**
|
||||
Return timing information (start time of console, current
|
||||
number of frames rendered, etc.
|
||||
*/
|
||||
const TimingInfo& timingInfo() const { return myTimingInfo; }
|
||||
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The following methods are system-specific and can be overrided in
|
||||
|
@ -406,6 +384,8 @@ class OSystem
|
|||
*/
|
||||
virtual uInt64 getTicks() const;
|
||||
|
||||
float frameRate() const;
|
||||
|
||||
/**
|
||||
This method runs the main loop. Since different platforms
|
||||
may use different timing methods and/or algorithms, this method can
|
||||
|
@ -504,18 +484,12 @@ class OSystem
|
|||
// The list of log messages
|
||||
string myLogMessages;
|
||||
|
||||
// Number of times per second to iterate through the main loop
|
||||
float myDisplayFrameRate;
|
||||
|
||||
// Time per frame for a video update, based on the current framerate
|
||||
uInt32 myTimePerFrame;
|
||||
|
||||
// The time (in milliseconds) from the UNIX epoch when the application starts
|
||||
uInt32 myMillisAtStart;
|
||||
|
||||
// Indicates whether to stop the main loop
|
||||
bool myQuitLoop;
|
||||
|
||||
// Audio settings
|
||||
AudioSettings myAudioSettings;
|
||||
|
||||
private:
|
||||
string myBaseDir;
|
||||
string myStateDir;
|
||||
|
@ -536,9 +510,6 @@ class OSystem
|
|||
string myFeatures;
|
||||
string myBuildInfo;
|
||||
|
||||
// Indicates whether the main processing loop should proceed
|
||||
TimingInfo myTimingInfo;
|
||||
|
||||
private:
|
||||
/**
|
||||
Creates the various framebuffers/renderers available in this system.
|
||||
|
@ -590,12 +561,6 @@ class OSystem
|
|||
*/
|
||||
string getROMInfo(const Console& console);
|
||||
|
||||
/**
|
||||
Initializes the timing so that the mainloop is reset to its
|
||||
initial values.
|
||||
*/
|
||||
void resetLoopTiming();
|
||||
|
||||
/**
|
||||
Validate the directory name, and create it if necessary.
|
||||
Also, update the settings with the new name. For now, validation
|
||||
|
@ -608,6 +573,8 @@ class OSystem
|
|||
void validatePath(string& path, const string& setting,
|
||||
const string& defaultpath);
|
||||
|
||||
double dispatchEmulation(EmulationWorker& emulationWorker);
|
||||
|
||||
// Following constructors and assignment operators not supported
|
||||
OSystem(const OSystem&) = delete;
|
||||
OSystem(OSystem&&) = delete;
|
||||
|
|
|
@ -115,7 +115,7 @@ class PropertiesSet
|
|||
/**
|
||||
Return the size of the myExternalProps list
|
||||
*/
|
||||
uInt32 size() { return myExternalProps.size(); }
|
||||
uInt64 size() const { return myExternalProps.size(); }
|
||||
|
||||
private:
|
||||
using PropsList = std::map<string, Properties>;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "OSystem.hxx"
|
||||
#include "Version.hxx"
|
||||
#include "AudioSettings.hxx"
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
#include "DebuggerDialog.hxx"
|
||||
|
@ -34,7 +35,7 @@ Settings::Settings(OSystem& osystem)
|
|||
{
|
||||
// Video-related options
|
||||
setInternal("video", "");
|
||||
setInternal("framerate", "0");
|
||||
setInternal("speed", "1.0");
|
||||
setInternal("vsync", "true");
|
||||
setInternal("fullscreen", "false");
|
||||
setInternal("center", "false");
|
||||
|
@ -69,10 +70,14 @@ Settings::Settings(OSystem& osystem)
|
|||
setInternal("tv.bleed", "0.0");
|
||||
|
||||
// Sound options
|
||||
setInternal("sound", "true");
|
||||
setInternal("fragsize", "512");
|
||||
setInternal("freq", "31400");
|
||||
setInternal("volume", "100");
|
||||
setInternal(AudioSettings::SETTING_ENABLED, AudioSettings::DEFAULT_ENABLED);
|
||||
setInternal(AudioSettings::SETTING_PRESET, static_cast<int>(AudioSettings::DEFAULT_PRESET));
|
||||
setInternal(AudioSettings::SETTING_SAMPLE_RATE, AudioSettings::DEFAULT_SAMPLE_RATE);
|
||||
setInternal(AudioSettings::SETTING_FRAGMENT_SIZE, AudioSettings::DEFAULT_FRAGMENT_SIZE);
|
||||
setInternal(AudioSettings::SETTING_BUFFER_SIZE, AudioSettings::DEFAULT_BUFFER_SIZE);
|
||||
setInternal(AudioSettings::SETTING_HEADROOM, AudioSettings::DEFAULT_HEADROOM);
|
||||
setInternal(AudioSettings::SETTING_RESAMPLING_QUALITY, static_cast<int>(AudioSettings::DEFAULT_RESAMPLING_QUALITY));
|
||||
setInternal(AudioSettings::SETTING_VOLUME, AudioSettings::DEFAULT_VOLUME);
|
||||
|
||||
// Input event options
|
||||
setInternal("keymap", "");
|
||||
|
@ -298,6 +303,10 @@ void Settings::validate()
|
|||
{
|
||||
string s;
|
||||
int i;
|
||||
float f;
|
||||
|
||||
f = getFloat("speed");
|
||||
if (f <= 0) setInternal("speed", "1.0");
|
||||
|
||||
s = getString("timing");
|
||||
if(s != "sleep" && s != "busy") setInternal("timing", "sleep");
|
||||
|
@ -359,11 +368,7 @@ void Settings::validate()
|
|||
if(i < 0 || i > 6) setInternal("plr.tm.horizon", 5);*/
|
||||
|
||||
#ifdef SOUND_SUPPORT
|
||||
i = getInt("volume");
|
||||
if(i < 0 || i > 100) setInternal("volume", "100");
|
||||
i = getInt("freq");
|
||||
if(!(i == 11025 || i == 22050 || i == 31400 || i == 44100 || i == 48000))
|
||||
setInternal("freq", "31400");
|
||||
AudioSettings::normalize(*this);
|
||||
#endif
|
||||
|
||||
i = getInt("joydeadzone");
|
||||
|
@ -438,15 +443,19 @@ void Settings::usage() const
|
|||
<< " -palette <standard| Use the specified color palette\n"
|
||||
<< " z26|\n"
|
||||
<< " user>\n"
|
||||
<< " -framerate <number> Display the given number of frames per second (0 to auto-calculate)\n"
|
||||
<< " -speed <number> Run emulation at the given speed\n"
|
||||
<< " -timing <sleep|busy> Use the given type of wait between frames\n"
|
||||
<< " -uimessages <1|0> Show onscreen UI messages for different events\n"
|
||||
<< endl
|
||||
#ifdef SOUND_SUPPORT
|
||||
<< " -sound <1|0> Enable sound generation\n"
|
||||
<< " -fragsize <number> The size of sound fragments (must be a power of two)\n"
|
||||
<< " -freq <number> Set sound sample output frequency (11025|22050|31400|44100|48000)\n"
|
||||
<< " -volume <number> Set the volume (0 - 100)\n"
|
||||
<< " -audio.enabled <1|0> Enable audio\n"
|
||||
<< " -audio.preset <1-5> Audio preset (or 1 for custom)\n"
|
||||
<< " -audio.sample_rate <number> Output sample rate (44100|48000|96000)\n"
|
||||
<< " -audio.fragment_size <number> Fragment size (128|256|512|1024|2048|4096)\n"
|
||||
<< " -audio.buffer_size <number> Max. number of additional half-frames to buffer (0 -- 20)\n"
|
||||
<< " -audio.headroom <number> Additional half-frames to prebuffer (0 -- 20)\n"
|
||||
<< " -audio.resampling_quality <1-3> Resampling quality\n"
|
||||
<< " -audio.volume <number> Vokume (0 -- 100)\n"
|
||||
<< endl
|
||||
#endif
|
||||
<< " -tia.zoom <zoom> Use the specified zoom level (windowed mode) for TIA image\n"
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
#define SOUND_HXX
|
||||
|
||||
class OSystem;
|
||||
class AudioQueue;
|
||||
class EmulationTiming;
|
||||
|
||||
#include "Serializable.hxx"
|
||||
#include "bspf.hxx"
|
||||
|
||||
/**
|
||||
|
@ -29,7 +30,7 @@ class OSystem;
|
|||
|
||||
@author Stephen Anthony
|
||||
*/
|
||||
class Sound : public Serializable
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
@ -47,26 +48,11 @@ class Sound : public Serializable
|
|||
*/
|
||||
virtual void setEnabled(bool enable) = 0;
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound).
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
virtual void setChannels(uInt32 channels) = 0;
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
virtual void setFrameRate(float framerate) = 0;
|
||||
|
||||
/**
|
||||
Start the sound system, initializing it if necessary. This must be
|
||||
called before any calls are made to derived methods.
|
||||
*/
|
||||
virtual void open() = 0;
|
||||
virtual void open(shared_ptr<AudioQueue>, EmulationTiming*) = 0;
|
||||
|
||||
/**
|
||||
Should be called to stop the sound system. Once called the sound
|
||||
|
@ -81,20 +67,21 @@ class Sound : public Serializable
|
|||
*/
|
||||
virtual void mute(bool state) = 0;
|
||||
|
||||
/**
|
||||
Get the fragment size.
|
||||
*/
|
||||
virtual uInt32 getFragmentSize() const = 0;
|
||||
|
||||
/**
|
||||
Get the sample rate.
|
||||
*/
|
||||
virtual uInt32 getSampleRate() const = 0;
|
||||
|
||||
/**
|
||||
Reset the sound device.
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
virtual void set(uInt16 addr, uInt8 value, uInt64 cycle) = 0;
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
|
@ -102,7 +89,7 @@ class Sound : public Serializable
|
|||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
*/
|
||||
virtual void setVolume(Int32 percent) = 0;
|
||||
virtual void setVolume(uInt32 percent) = 0;
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -73,10 +73,9 @@ class TIASurface
|
|||
const FBSurface& baseSurface(GUI::Rect& rect) const;
|
||||
|
||||
/**
|
||||
Get the TIA pixel associated with the given TIA buffer index,
|
||||
shifting by the given offset (for greyscale values).
|
||||
*/
|
||||
uInt32 pixel(uInt32 idx, uInt8 shift = 0);
|
||||
Use the palette to map a single indexed pixel color. This is used by the TIA output widget.
|
||||
*/
|
||||
uInt32 mapIndexedPixel(uInt8 indexedColor, uInt8 shift = 0);
|
||||
|
||||
/**
|
||||
Get the NTSCFilter object associated with the framebuffer
|
||||
|
|
|
@ -51,8 +51,11 @@ MODULE_OBJS := \
|
|||
src/emucore/CompuMate.o \
|
||||
src/emucore/Console.o \
|
||||
src/emucore/Control.o \
|
||||
src/emucore/DispatchResult.o \
|
||||
src/emucore/Driving.o \
|
||||
src/emucore/EventHandler.o \
|
||||
src/emucore/EmulationTiming.o \
|
||||
src/emucore/EmulationWorker.o \
|
||||
src/emucore/FrameBuffer.o \
|
||||
src/emucore/FBSurface.o \
|
||||
src/emucore/FSNode.o \
|
||||
|
@ -75,7 +78,6 @@ MODULE_OBJS := \
|
|||
src/emucore/Settings.o \
|
||||
src/emucore/Switches.o \
|
||||
src/emucore/System.o \
|
||||
src/emucore/TIASnd.o \
|
||||
src/emucore/TIASurface.o \
|
||||
src/emucore/Thumbulator.o
|
||||
|
||||
|
|
|
@ -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;
|
||||
myIsDumped = false;
|
||||
|
@ -38,7 +38,7 @@ void PaddleReader::reset(double timestamp)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void PaddleReader::vblank(uInt8 value, double timestamp)
|
||||
void PaddleReader::vblank(uInt8 value, uInt64 timestamp)
|
||||
{
|
||||
bool oldIsDumped = myIsDumped;
|
||||
|
||||
|
@ -53,7 +53,7 @@ void PaddleReader::vblank(uInt8 value, double timestamp)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt8 PaddleReader::inpt(double timestamp)
|
||||
uInt8 PaddleReader::inpt(uInt64 timestamp)
|
||||
{
|
||||
updateCharge(timestamp);
|
||||
|
||||
|
@ -63,7 +63,7 @@ uInt8 PaddleReader::inpt(double timestamp)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void PaddleReader::update(double value, double timestamp, ConsoleTiming consoleTiming)
|
||||
void PaddleReader::update(double value, uInt64 timestamp, ConsoleTiming consoleTiming)
|
||||
{
|
||||
if (consoleTiming != myConsoleTiming) {
|
||||
setConsoleTiming(consoleTiming);
|
||||
|
@ -94,13 +94,13 @@ void PaddleReader::setConsoleTiming(ConsoleTiming consoleTiming)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void PaddleReader::updateCharge(double timestamp)
|
||||
void PaddleReader::updateCharge(uInt64 timestamp)
|
||||
{
|
||||
if (myIsDumped) return;
|
||||
|
||||
if (myValue >= 0)
|
||||
myU = USUPP * (1 - (1 - myU / USUPP) *
|
||||
exp(-(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq));
|
||||
exp(-static_cast<double>(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq));
|
||||
|
||||
myTimestamp = timestamp;
|
||||
}
|
||||
|
|
|
@ -30,14 +30,14 @@ class PaddleReader : public Serializable
|
|||
|
||||
public:
|
||||
|
||||
void reset(double timestamp);
|
||||
void reset(uInt64 timestamp);
|
||||
|
||||
void vblank(uInt8 value, double timestamp);
|
||||
void vblank(uInt8 value, uInt64 timestamp);
|
||||
bool vblankDumped() const { return myIsDumped; }
|
||||
|
||||
uInt8 inpt(double timestamp);
|
||||
uInt8 inpt(uInt64 timestamp);
|
||||
|
||||
void update(double value, double timestamp, ConsoleTiming consoleTiming);
|
||||
void update(double value, uInt64 timestamp, ConsoleTiming consoleTiming);
|
||||
|
||||
/**
|
||||
Serializable methods (see that class for more information).
|
||||
|
@ -50,7 +50,7 @@ class PaddleReader : public Serializable
|
|||
|
||||
void setConsoleTiming(ConsoleTiming timing);
|
||||
|
||||
void updateCharge(double timestamp);
|
||||
void updateCharge(uInt64 timestamp);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -58,7 +58,7 @@ class PaddleReader : public Serializable
|
|||
double myU;
|
||||
|
||||
double myValue;
|
||||
double myTimestamp;
|
||||
uInt64 myTimestamp;
|
||||
|
||||
ConsoleTiming myConsoleTiming;
|
||||
double myClockFreq;
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "DelayQueueIteratorImpl.hxx"
|
||||
#include "TIAConstants.hxx"
|
||||
#include "frame-manager/FrameManager.hxx"
|
||||
#include "AudioQueue.hxx"
|
||||
#include "DispatchResult.hxx"
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
#include "CartDebug.hxx"
|
||||
|
@ -65,9 +67,8 @@ enum ResxCounter: uInt8 {
|
|||
static constexpr uInt8 resxLateHblankThreshold = 73;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
TIA::TIA(Console& console, Sound& sound, Settings& settings)
|
||||
TIA::TIA(Console& console, Settings& settings)
|
||||
: myConsole(console),
|
||||
mySound(sound),
|
||||
mySettings(settings),
|
||||
myFrameManager(nullptr),
|
||||
myPlayfield(~CollisionMask::playfield & 0x7FFF),
|
||||
|
@ -116,6 +117,12 @@ void TIA::setFrameManager(AbstractFrameManager *frameManager)
|
|||
myFrameManager->setJitterFactor(myJitterFactor);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::setAudioQueue(shared_ptr<AudioQueue> queue)
|
||||
{
|
||||
myAudio.setAudioQueue(queue);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::clearFrameManager()
|
||||
{
|
||||
|
@ -138,7 +145,6 @@ void TIA::reset()
|
|||
myCollisionMask = 0;
|
||||
myLinesSinceChange = 0;
|
||||
myCollisionUpdateRequired = false;
|
||||
myAutoFrameEnabled = false;
|
||||
myColorLossEnabled = myColorLossActive = false;
|
||||
myColorHBlank = 0;
|
||||
myLastCycle = 0;
|
||||
|
@ -159,11 +165,12 @@ void TIA::reset()
|
|||
myInput0.reset();
|
||||
myInput1.reset();
|
||||
|
||||
myAudio.reset();
|
||||
|
||||
myTimestamp = 0;
|
||||
for (PaddleReader& paddleReader : myPaddleReaders)
|
||||
paddleReader.reset(myTimestamp);
|
||||
|
||||
mySound.reset();
|
||||
myDelayQueue.reset();
|
||||
|
||||
myCyclesAtFrameStart = 0;
|
||||
|
@ -174,6 +181,11 @@ void TIA::reset()
|
|||
frameReset(); // Recalculate the size of the display
|
||||
}
|
||||
|
||||
myFrontBufferFrameRate = myFrameBufferFrameRate = 0;
|
||||
myFrontBufferScanlines = myFrameBufferScanlines = 0;
|
||||
|
||||
myNewFramePending = false;
|
||||
|
||||
// Must be done last, after all other items have reset
|
||||
enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors"));
|
||||
setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
|
||||
|
@ -186,8 +198,9 @@ void TIA::reset()
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::frameReset()
|
||||
{
|
||||
memset(myBackBuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||
memset(myFrontBuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||
memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight);
|
||||
myAutoFrameEnabled = mySettings.getInt("framerate") <= 0;
|
||||
enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss");
|
||||
}
|
||||
|
||||
|
@ -227,8 +240,6 @@ bool TIA::save(Serializer& out) const
|
|||
{
|
||||
out.putString(name());
|
||||
|
||||
if(!mySound.save(out)) return false;
|
||||
|
||||
if(!myDelayQueue.save(out)) return false;
|
||||
if(!myFrameManager->save(out)) return false;
|
||||
|
||||
|
@ -239,6 +250,7 @@ bool TIA::save(Serializer& out) const
|
|||
if(!myPlayer0.save(out)) return false;
|
||||
if(!myPlayer1.save(out)) return false;
|
||||
if(!myBall.save(out)) return false;
|
||||
if(!myAudio.save(out)) return false;
|
||||
|
||||
for (const PaddleReader& paddleReader : myPaddleReaders)
|
||||
if(!paddleReader.save(out)) return false;
|
||||
|
@ -275,11 +287,14 @@ bool TIA::save(Serializer& out) const
|
|||
|
||||
out.putLong(myTimestamp);
|
||||
|
||||
out.putBool(myAutoFrameEnabled);
|
||||
|
||||
out.putByteArray(myShadowRegisters, 64);
|
||||
|
||||
out.putLong(myCyclesAtFrameStart);
|
||||
|
||||
out.putInt(myFrameBufferScanlines);
|
||||
out.putInt(myFrontBufferScanlines);
|
||||
out.putDouble(myFrameBufferFrameRate);
|
||||
out.putDouble(myFrontBufferFrameRate);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -298,8 +313,6 @@ bool TIA::load(Serializer& in)
|
|||
if(in.getString() != name())
|
||||
return false;
|
||||
|
||||
if(!mySound.load(in)) return false;
|
||||
|
||||
if(!myDelayQueue.load(in)) return false;
|
||||
if(!myFrameManager->load(in)) return false;
|
||||
|
||||
|
@ -310,6 +323,7 @@ bool TIA::load(Serializer& in)
|
|||
if(!myPlayer0.load(in)) return false;
|
||||
if(!myPlayer1.load(in)) return false;
|
||||
if(!myBall.load(in)) return false;
|
||||
if(!myAudio.load(in)) return false;
|
||||
|
||||
for (PaddleReader& paddleReader : myPaddleReaders)
|
||||
if(!paddleReader.load(in)) return false;
|
||||
|
@ -346,11 +360,14 @@ bool TIA::load(Serializer& in)
|
|||
|
||||
myTimestamp = in.getLong();
|
||||
|
||||
myAutoFrameEnabled = in.getBool();
|
||||
|
||||
in.getByteArray(myShadowRegisters, 64);
|
||||
|
||||
myCyclesAtFrameStart = in.getLong();
|
||||
|
||||
myFrameBufferScanlines = in.getInt();
|
||||
myFrontBufferScanlines = in.getInt();
|
||||
myFrameBufferFrameRate = in.getDouble();
|
||||
myFrontBufferFrameRate = in.getDouble();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -521,33 +538,35 @@ bool TIA::poke(uInt16 address, uInt8 value)
|
|||
|
||||
break;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// FIXME - rework this when we add the new sound core
|
||||
case AUDV0:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel0().audv(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
|
||||
case AUDV1:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel1().audv(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
|
||||
case AUDF0:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel0().audf(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
|
||||
case AUDF1:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel1().audf(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
|
||||
case AUDC0:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel0().audc(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
|
||||
case AUDC1:
|
||||
mySound.set(address, value, mySystem->cycles());
|
||||
myAudio.channel1().audc(value);
|
||||
myShadowRegisters[address] = value;
|
||||
break;
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
case HMOVE:
|
||||
myDelayQueue.push(HMOVE, value, Delay::hmove);
|
||||
|
@ -777,7 +796,10 @@ bool TIA::saveDisplay(Serializer& out) const
|
|||
{
|
||||
try
|
||||
{
|
||||
out.putByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
|
||||
out.putByteArray(myFramebuffer, 160* TIAConstants::frameBufferHeight);
|
||||
out.putByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
out.putByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
out.putBool(myNewFramePending);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -795,6 +817,9 @@ bool TIA::loadDisplay(Serializer& in)
|
|||
{
|
||||
// Reset frame buffer pointer and data
|
||||
in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight);
|
||||
in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
in.getByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
myNewFramePending = in.getBool();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -806,9 +831,30 @@ bool TIA::loadDisplay(Serializer& in)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::update()
|
||||
void TIA::update(DispatchResult& result, uInt32 maxCycles)
|
||||
{
|
||||
mySystem->m6502().execute(25000);
|
||||
mySystem->m6502().execute(maxCycles, result);
|
||||
|
||||
updateEmulation();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::renderToFrameBuffer()
|
||||
{
|
||||
if (!myNewFramePending) return;
|
||||
|
||||
memcpy(myFramebuffer, myFrontBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
|
||||
myFrameBufferFrameRate = myFrontBufferFrameRate;
|
||||
myFrameBufferScanlines = myFrontBufferScanlines;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::update(uInt32 maxCycles)
|
||||
{
|
||||
DispatchResult dispatchResult;
|
||||
|
||||
update(dispatchResult, maxCycles);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1143,16 +1189,19 @@ void TIA::onFrameComplete()
|
|||
myCyclesAtFrameStart = mySystem->cycles();
|
||||
|
||||
if (myXAtRenderingStart > 0)
|
||||
memset(myFramebuffer, 0, myXAtRenderingStart);
|
||||
memset(myBackBuffer, 0, myXAtRenderingStart);
|
||||
|
||||
// Blank out any extra lines not drawn this frame
|
||||
const Int32 missingScanlines = myFrameManager->missingScanlines();
|
||||
if (missingScanlines > 0)
|
||||
memset(myFramebuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160);
|
||||
memset(myBackBuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160);
|
||||
|
||||
// Recalculate framerate, attempting to auto-correct for scanline 'jumps'
|
||||
if(myAutoFrameEnabled)
|
||||
myConsole.setFramerate(myFrameManager->frameRate());
|
||||
memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight);
|
||||
|
||||
myFrontBufferFrameRate = frameRate();
|
||||
myFrontBufferScanlines = scanlinesLastFrame();
|
||||
|
||||
myNewFramePending = true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1188,6 +1237,8 @@ void TIA::cycle(uInt32 colorClocks)
|
|||
if (++myHctr >= 228)
|
||||
nextLine();
|
||||
|
||||
myAudio.tick();
|
||||
|
||||
myTimestamp++;
|
||||
}
|
||||
}
|
||||
|
@ -1261,7 +1312,7 @@ void TIA::applyRsync()
|
|||
|
||||
myHctrDelta = 225 - myHctr;
|
||||
if (myFrameManager->isRendering())
|
||||
memset(myFramebuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
|
||||
memset(myBackBuffer + myFrameManager->getY() * 160 + x, 0, 160 - x);
|
||||
|
||||
myHctr = 225;
|
||||
}
|
||||
|
@ -1300,7 +1351,7 @@ void TIA::cloneLastLine()
|
|||
|
||||
if (!myFrameManager->isRendering() || y == 0) return;
|
||||
|
||||
uInt8* buffer = myFramebuffer;
|
||||
uInt8* buffer = myBackBuffer;
|
||||
|
||||
memcpy(buffer + y * 160, buffer + (y-1) * 160, 160);
|
||||
}
|
||||
|
@ -1373,7 +1424,7 @@ void TIA::renderPixel(uInt32 x, uInt32 y)
|
|||
}
|
||||
}
|
||||
|
||||
myFramebuffer[y * 160 + x] = color;
|
||||
myBackBuffer[y * 160 + x] = color;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1399,7 +1450,7 @@ void TIA::flushLineCache()
|
|||
void TIA::clearHmoveComb()
|
||||
{
|
||||
if (myFrameManager->isRendering() && myHstate == HState::blank)
|
||||
memset(myFramebuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
|
||||
memset(myBackBuffer + myFrameManager->getY() * 160, myColorHBlank, 8);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#include "bspf.hxx"
|
||||
#include "Console.hxx"
|
||||
#include "Sound.hxx"
|
||||
#include "Settings.hxx"
|
||||
#include "Device.hxx"
|
||||
#include "Serializer.hxx"
|
||||
|
@ -29,6 +28,7 @@
|
|||
#include "DelayQueueIterator.hxx"
|
||||
#include "frame-manager/AbstractFrameManager.hxx"
|
||||
#include "FrameLayout.hxx"
|
||||
#include "Audio.hxx"
|
||||
#include "Background.hxx"
|
||||
#include "Playfield.hxx"
|
||||
#include "Missile.hxx"
|
||||
|
@ -40,6 +40,9 @@
|
|||
#include "Control.hxx"
|
||||
#include "System.hxx"
|
||||
|
||||
class AudioQueue;
|
||||
class DispatchResult;
|
||||
|
||||
/**
|
||||
This class is a device that emulates the Television Interface Adaptor
|
||||
found in the Atari 2600 and 7800 consoles. The Television Interface
|
||||
|
@ -96,21 +99,26 @@ class TIA : public Device
|
|||
Create a new TIA for the specified console
|
||||
|
||||
@param console The console the TIA is associated with
|
||||
@param sound The sound object the TIA is associated with
|
||||
@param settings The settings object for this TIA device
|
||||
*/
|
||||
TIA(Console& console, Sound& sound, Settings& settings);
|
||||
TIA(Console& console, Settings& settings);
|
||||
|
||||
virtual ~TIA() = default;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Configure the frame manager.
|
||||
Configure the frame manager.
|
||||
*/
|
||||
void setFrameManager(AbstractFrameManager *frameManager);
|
||||
|
||||
/**
|
||||
* Clear the configured frame manager and deteach the lifecycle callbacks.
|
||||
Set the audio queue. This needs to be dynamic as the queue is created after
|
||||
the timing has been determined.
|
||||
*/
|
||||
void setAudioQueue(shared_ptr<AudioQueue> audioQueue);
|
||||
|
||||
/**
|
||||
Clear the configured frame manager and deteach the lifecycle callbacks.
|
||||
*/
|
||||
void clearFrameManager();
|
||||
|
||||
|
@ -192,7 +200,25 @@ class TIA : public Device
|
|||
desired frame rate to update the TIA. Invoking this method will update
|
||||
the graphics buffer and generate the corresponding audio samples.
|
||||
*/
|
||||
void update();
|
||||
void update(DispatchResult& result, uInt32 maxCycles = 50000);
|
||||
|
||||
void update(uInt32 maxCycles = 50000);
|
||||
|
||||
/**
|
||||
Did we generate a new frame?
|
||||
*/
|
||||
bool newFramePending() { return myNewFramePending; }
|
||||
|
||||
/**
|
||||
Render the pending frame to the framebuffer and clear the flag.
|
||||
*/
|
||||
void renderToFrameBuffer();
|
||||
|
||||
/**
|
||||
Return the buffer that holds the currently drawing TIA frame
|
||||
(the TIA output widget needs this).
|
||||
*/
|
||||
uInt8* outputBuffer() { return myBackBuffer; }
|
||||
|
||||
/**
|
||||
Returns a pointer to the internal frame buffer.
|
||||
|
@ -222,13 +248,12 @@ class TIA : public Device
|
|||
*/
|
||||
ConsoleTiming consoleTiming() const { return myConsole.timing(); }
|
||||
|
||||
/**
|
||||
Enables/disables auto-frame calculation. If enabled, the TIA
|
||||
re-adjusts the framerate at regular intervals.
|
||||
float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; }
|
||||
|
||||
@param enabled Whether to enable or disable all auto-frame calculation
|
||||
*/
|
||||
void enableAutoFrame(bool enabled) { myAutoFrameEnabled = enabled; }
|
||||
/**
|
||||
The same, but for the frame in the frame buffer.
|
||||
*/
|
||||
float frameBufferFrameRate() const { return myFrameBufferFrameRate; }
|
||||
|
||||
/**
|
||||
Enables/disables color-loss for PAL modes only.
|
||||
|
@ -275,6 +300,11 @@ class TIA : public Device
|
|||
*/
|
||||
uInt32 scanlinesLastFrame() const { return myFrameManager->scanlinesLastFrame(); }
|
||||
|
||||
/**
|
||||
The same, but for the frame in the frame buffer.
|
||||
*/
|
||||
uInt32 frameBufferScanlinesLastFrame() const { return myFrameBufferScanlines; }
|
||||
|
||||
/**
|
||||
Answers the total system cycles from the start of the emulation.
|
||||
*/
|
||||
|
@ -604,7 +634,6 @@ class TIA : public Device
|
|||
private:
|
||||
|
||||
Console& myConsole;
|
||||
Sound& mySound;
|
||||
Settings& mySettings;
|
||||
|
||||
/**
|
||||
|
@ -641,6 +670,8 @@ class TIA : public Device
|
|||
Player myPlayer1;
|
||||
Ball myBall;
|
||||
|
||||
Audio myAudio;
|
||||
|
||||
/**
|
||||
* The paddle readout circuits.
|
||||
*/
|
||||
|
@ -655,6 +686,19 @@ class TIA : public Device
|
|||
// Pointer to the internal color-index-based frame buffer
|
||||
uInt8 myFramebuffer[160 * TIAConstants::frameBufferHeight];
|
||||
|
||||
// The frame is rendered to the backbuffer and only copied to the framebuffer
|
||||
// upon completion
|
||||
uInt8 myBackBuffer[160 * TIAConstants::frameBufferHeight];
|
||||
uInt8 myFrontBuffer[160 * TIAConstants::frameBufferHeight];
|
||||
|
||||
// We snapshot frame statistics when the back buffer is copied to the front buffer
|
||||
// and when the front buffer is copied to the frame buffer
|
||||
uInt32 myFrontBufferScanlines, myFrameBufferScanlines;
|
||||
float myFrontBufferFrameRate, myFrameBufferFrameRate;
|
||||
|
||||
// Did we emit a frame?
|
||||
bool myNewFramePending;
|
||||
|
||||
/**
|
||||
* Setting this to true injects random values into undefined reads.
|
||||
*/
|
||||
|
@ -740,11 +784,9 @@ class TIA : public Device
|
|||
uInt8 myColorHBlank;
|
||||
|
||||
/**
|
||||
* The total number of color clocks since emulation started. This is a
|
||||
* double a) to avoid overflows and b) as it will enter floating point
|
||||
* expressions in the paddle readout simulation anyway.
|
||||
* The total number of color clocks since emulation started.
|
||||
*/
|
||||
double myTimestamp;
|
||||
uInt64 myTimestamp;
|
||||
|
||||
/**
|
||||
* The "shadow registers" track the last written register value for the
|
||||
|
@ -752,11 +794,6 @@ class TIA : public Device
|
|||
*/
|
||||
uInt8 myShadowRegisters[64];
|
||||
|
||||
/**
|
||||
* Automatic framerate correction based on number of scanlines.
|
||||
*/
|
||||
bool myAutoFrameEnabled;
|
||||
|
||||
/**
|
||||
* Indicates if color loss should be enabled or disabled. Color loss
|
||||
* occurs on PAL-like systems when the previous frame contains an odd
|
||||
|
|
|
@ -47,9 +47,7 @@ FrameManager::FrameManager()
|
|||
myHeight(0),
|
||||
myFixedHeight(0),
|
||||
myYStart(0),
|
||||
myJitterEnabled(false),
|
||||
myStableFrameLines(-1),
|
||||
myStableFrameHeightCountdown(0)
|
||||
myJitterEnabled(false)
|
||||
{
|
||||
onLayoutChange();
|
||||
}
|
||||
|
@ -63,9 +61,6 @@ void FrameManager::onReset()
|
|||
myVsyncLines = 0;
|
||||
myY = 0;
|
||||
|
||||
myStableFrameLines = -1;
|
||||
myStableFrameHeightCountdown = 0;
|
||||
|
||||
myJitterEmulation.reset();
|
||||
}
|
||||
|
||||
|
@ -230,9 +225,6 @@ bool FrameManager::onSave(Serializer& out) const
|
|||
|
||||
out.putBool(myJitterEnabled);
|
||||
|
||||
out.putInt(myStableFrameLines);
|
||||
out.putInt(myStableFrameHeightCountdown);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -257,8 +249,5 @@ bool FrameManager::onLoad(Serializer& in)
|
|||
|
||||
myJitterEnabled = in.getBool();
|
||||
|
||||
myStableFrameLines = in.getInt();
|
||||
myStableFrameHeightCountdown = in.getInt();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -100,9 +100,6 @@ class FrameManager: public AbstractFrameManager {
|
|||
|
||||
bool myJitterEnabled;
|
||||
|
||||
Int32 myStableFrameLines;
|
||||
uInt8 myStableFrameHeightCountdown;
|
||||
|
||||
JitterEmulation myJitterEmulation;
|
||||
|
||||
private:
|
||||
|
|
|
@ -9,7 +9,9 @@ MODULE_OBJS := \
|
|||
src/emucore/tia/Ball.o \
|
||||
src/emucore/tia/Background.o \
|
||||
src/emucore/tia/LatchedInput.o \
|
||||
src/emucore/tia/PaddleReader.o
|
||||
src/emucore/tia/PaddleReader.o \
|
||||
src/emucore/tia/Audio.o \
|
||||
src/emucore/tia/AudioChannel.o
|
||||
|
||||
MODULE_DIRS += \
|
||||
src/emucore/tia
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "Settings.hxx"
|
||||
#include "Sound.hxx"
|
||||
#include "Widget.hxx"
|
||||
#include "AudioSettings.hxx"
|
||||
|
||||
#include "AudioDialog.hxx"
|
||||
|
||||
|
@ -44,14 +45,14 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
|||
fontWidth = font.getMaxCharWidth(),
|
||||
fontHeight = font.getFontHeight();
|
||||
int xpos, ypos;
|
||||
int lwidth = font.getStringWidth("Sample Size (*) "),
|
||||
int lwidth = font.getStringWidth("Resampling quality "),
|
||||
pwidth = font.getStringWidth("512 bytes");
|
||||
WidgetArray wid;
|
||||
VariantList items;
|
||||
|
||||
// Set real dimensions
|
||||
_w = 35 * fontWidth + HBORDER * 2;
|
||||
_h = 7 * (lineHeight + 4) + VBORDER + _th;
|
||||
_w = 45 * fontWidth + HBORDER * 2;
|
||||
_h = 11 * (lineHeight + 4) + VBORDER + _th;
|
||||
|
||||
xpos = HBORDER; ypos = VBORDER + _th;
|
||||
|
||||
|
@ -64,43 +65,80 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
|||
xpos += INDENT;
|
||||
|
||||
// Volume
|
||||
myVolumeSlider = new SliderWidget(this, font, xpos, ypos, 11 * fontWidth + 5, lineHeight,
|
||||
"Volume ", lwidth, 0, 4 * fontWidth, "%");
|
||||
myVolumeSlider = new SliderWidget(this, font, xpos, ypos,
|
||||
"Volume ", 0, 0, 4 * fontWidth, "%");
|
||||
myVolumeSlider->setMinValue(1); myVolumeSlider->setMaxValue(100);
|
||||
wid.push_back(myVolumeSlider);
|
||||
ypos += lineHeight + 4;
|
||||
|
||||
//
|
||||
VarList::push_back(items, "Low quality, medium lag", static_cast<int>(AudioSettings::Preset::lowQualityMediumLag));
|
||||
VarList::push_back(items, "High quality, medium lag", static_cast<int>(AudioSettings::Preset::highQualityMediumLag));
|
||||
VarList::push_back(items, "High quality, low lag", static_cast<int>(AudioSettings::Preset::highQualityLowLag));
|
||||
VarList::push_back(items, "Ultra quality, minimal lag", static_cast<int>(AudioSettings::Preset::veryHighQualityVeryLowLag));
|
||||
VarList::push_back(items, "Custom", static_cast<int>(AudioSettings::Preset::custom));
|
||||
myModePopup = new PopUpWidget(this, font, xpos, ypos,
|
||||
font.getStringWidth("Ultry quality, minimal lag "), lineHeight,
|
||||
items, "Mode (*) ", 0, kModeChanged);
|
||||
wid.push_back(myModePopup);
|
||||
ypos += lineHeight + 4;
|
||||
xpos += INDENT;
|
||||
|
||||
// Fragment size
|
||||
VarList::push_back(items, "128 bytes", "128");
|
||||
VarList::push_back(items, "256 bytes", "256");
|
||||
VarList::push_back(items, "512 bytes", "512");
|
||||
VarList::push_back(items, "1 KB", "1024");
|
||||
VarList::push_back(items, "2 KB", "2048");
|
||||
VarList::push_back(items, "4 KB", "4096");
|
||||
items.clear();
|
||||
VarList::push_back(items, "128 bytes", 128);
|
||||
VarList::push_back(items, "256 bytes", 256);
|
||||
VarList::push_back(items, "512 bytes", 512);
|
||||
VarList::push_back(items, "1 KB", 1024);
|
||||
VarList::push_back(items, "2 KB", 2048);
|
||||
VarList::push_back(items, "4 KB", 4096);
|
||||
myFragsizePopup = new PopUpWidget(this, font, xpos, ypos,
|
||||
pwidth, lineHeight,
|
||||
items, "Sample size (*) ", lwidth);
|
||||
items, "Fragment size (*) ", lwidth);
|
||||
wid.push_back(myFragsizePopup);
|
||||
ypos += lineHeight + 4;
|
||||
|
||||
// Output frequency
|
||||
items.clear();
|
||||
VarList::push_back(items, "11025 Hz", "11025");
|
||||
VarList::push_back(items, "22050 Hz", "22050");
|
||||
VarList::push_back(items, "31400 Hz", "31400");
|
||||
VarList::push_back(items, "44100 Hz", "44100");
|
||||
VarList::push_back(items, "48000 Hz", "48000");
|
||||
VarList::push_back(items, "44100 Hz", 44100);
|
||||
VarList::push_back(items, "48000 Hz", 48000);
|
||||
VarList::push_back(items, "96000 Hz", 96000);
|
||||
myFreqPopup = new PopUpWidget(this, font, xpos, ypos,
|
||||
pwidth, lineHeight,
|
||||
items, "Frequency (*) ", lwidth);
|
||||
items, "Sample rate (*) ", lwidth);
|
||||
wid.push_back(myFreqPopup);
|
||||
ypos += lineHeight + 4;
|
||||
|
||||
// Resampling quality
|
||||
items.clear();
|
||||
VarList::push_back(items, "Low", static_cast<int>(AudioSettings::ResamplingQuality::nearestNeightbour));
|
||||
VarList::push_back(items, "High", static_cast<int>(AudioSettings::ResamplingQuality::lanczos_2));
|
||||
VarList::push_back(items, "Ultra", static_cast<int>(AudioSettings::ResamplingQuality::lanczos_3));
|
||||
myResamplingPopup = new PopUpWidget(this, font, xpos, ypos,
|
||||
pwidth, lineHeight,
|
||||
items, "Resampling quality ", lwidth);
|
||||
wid.push_back(myResamplingPopup);
|
||||
ypos += lineHeight + 4;
|
||||
|
||||
// Param 1
|
||||
myHeadroomSlider = new SliderWidget(this, font, xpos, ypos,
|
||||
"Headroom ", 0, 0, 2 * fontWidth);
|
||||
myHeadroomSlider->setMinValue(1); myHeadroomSlider->setMaxValue(AudioSettings::MAX_HEADROOM);
|
||||
wid.push_back(myHeadroomSlider);
|
||||
ypos += lineHeight + 4;
|
||||
|
||||
// Param 2
|
||||
myBufferSizeSlider = new SliderWidget(this, font, xpos, ypos,
|
||||
"Buffer size ", 0, 0, 2 * fontWidth);
|
||||
myBufferSizeSlider->setMinValue(1); myBufferSizeSlider->setMaxValue(AudioSettings::MAX_BUFFER_SIZE);
|
||||
wid.push_back(myBufferSizeSlider);
|
||||
|
||||
// Add message concerning usage
|
||||
ypos = _h - fontHeight * 2 - 24;
|
||||
const GUI::Font& infofont = instance().frameBuffer().infoFont();
|
||||
new StaticTextWidget(this, infofont, HBORDER, ypos,
|
||||
new StaticTextWidget(this, infofont, HBORDER, ypos, "(*) Requires application restart");/* ,
|
||||
font.getStringWidth("(*) Requires application restart"), fontHeight,
|
||||
"(*) Requires application restart", TextAlign::Left);
|
||||
"(*) Requires application restart", TextAlign::Left);*/
|
||||
|
||||
// Add Defaults, OK and Cancel buttons
|
||||
addDefaultsOKCancelBGroup(wid, font);
|
||||
|
@ -111,41 +149,69 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent,
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void AudioDialog::loadConfig()
|
||||
{
|
||||
AudioSettings& audioSettings = instance().audioSettings();
|
||||
|
||||
// Volume
|
||||
myVolumeSlider->setValue(instance().settings().getInt("volume"));
|
||||
|
||||
// Fragsize
|
||||
myFragsizePopup->setSelected(instance().settings().getString("fragsize"), "512");
|
||||
|
||||
// Output frequency
|
||||
myFreqPopup->setSelected(instance().settings().getString("freq"), "31400");
|
||||
myVolumeSlider->setValue(audioSettings.volume());
|
||||
|
||||
// Enable sound
|
||||
bool b = instance().settings().getBool("sound");
|
||||
mySoundEnableCheckbox->setState(b);
|
||||
mySoundEnableCheckbox->setState(audioSettings.enabled());
|
||||
|
||||
// Preset / mode
|
||||
myModePopup->setSelected(static_cast<int>(audioSettings.preset()));
|
||||
|
||||
updatePresetSettings(instance().audioSettings());
|
||||
|
||||
// Make sure that mutually-exclusive items are not enabled at the same time
|
||||
handleSoundEnableChange(b);
|
||||
handleSoundEnableChange(audioSettings.enabled());
|
||||
handleModeChange(audioSettings.enabled());
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void AudioDialog::updatePresetSettings(AudioSettings& audioSettings)
|
||||
{
|
||||
// Fragsize
|
||||
myFragsizePopup->setSelected(audioSettings.fragmentSize());
|
||||
|
||||
// Output frequency
|
||||
myFreqPopup->setSelected(audioSettings.sampleRate());
|
||||
|
||||
// Headroom
|
||||
myHeadroomSlider->setValue(audioSettings.headroom());
|
||||
|
||||
// Buffer size
|
||||
myBufferSizeSlider->setValue(audioSettings.bufferSize());
|
||||
|
||||
// Resampling quality
|
||||
myResamplingPopup->setSelected(static_cast<int>(audioSettings.resamplingQuality()));
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void AudioDialog::saveConfig()
|
||||
{
|
||||
Settings& settings = instance().settings();
|
||||
AudioSettings audioSettings = instance().audioSettings();
|
||||
|
||||
// Volume
|
||||
settings.setValue("volume", myVolumeSlider->getValue());
|
||||
audioSettings.setVolume(myVolumeSlider->getValue());
|
||||
instance().sound().setVolume(myVolumeSlider->getValue());
|
||||
|
||||
// Fragsize
|
||||
settings.setValue("fragsize", myFragsizePopup->getSelectedTag().toString());
|
||||
|
||||
// Output frequency
|
||||
settings.setValue("freq", myFreqPopup->getSelectedTag().toString());
|
||||
|
||||
// Enable/disable sound (requires a restart to take effect)
|
||||
audioSettings.setEnabled(mySoundEnableCheckbox->getState());
|
||||
instance().sound().setEnabled(mySoundEnableCheckbox->getState());
|
||||
|
||||
AudioSettings::Preset preset = static_cast<AudioSettings::Preset>(myModePopup->getSelectedTag().toInt());
|
||||
audioSettings.setPreset(preset);
|
||||
|
||||
if (preset == AudioSettings::Preset::custom) {
|
||||
|
||||
// Fragsize
|
||||
audioSettings.setFragmentSize(myFragsizePopup->getSelectedTag().toInt());
|
||||
audioSettings.setSampleRate(myFreqPopup->getSelectedTag().toInt());
|
||||
audioSettings.setHeadroom(myHeadroomSlider->getValue());
|
||||
audioSettings.setBufferSize(myBufferSizeSlider->getValue());
|
||||
audioSettings.setResamplingQuality(static_cast<AudioSettings::ResamplingQuality>(myResamplingPopup->getSelectedTag().toInt()));
|
||||
}
|
||||
|
||||
// Only force a re-initialization when necessary, since it can
|
||||
// be a time-consuming operation
|
||||
if(instance().hasConsole())
|
||||
|
@ -172,10 +238,33 @@ void AudioDialog::setDefaults()
|
|||
void AudioDialog::handleSoundEnableChange(bool active)
|
||||
{
|
||||
myVolumeSlider->setEnabled(active);
|
||||
myFragsizePopup->setEnabled(active);
|
||||
myFreqPopup->setEnabled(active);
|
||||
myModePopup->setEnabled(active);
|
||||
handleModeChange(active);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void AudioDialog::handleModeChange(bool active)
|
||||
{
|
||||
AudioSettings::Preset preset = static_cast<AudioSettings::Preset>(myModePopup->getSelectedTag().toInt());
|
||||
|
||||
AudioSettings audioSettings = instance().audioSettings();
|
||||
audioSettings.setPersistent(false);
|
||||
audioSettings.setPreset(preset);
|
||||
|
||||
(cout << "Preset: " << static_cast<int>(preset) << std::endl).flush();
|
||||
|
||||
updatePresetSettings(audioSettings);
|
||||
|
||||
bool userMode = active && preset == AudioSettings::Preset::custom;
|
||||
|
||||
myFragsizePopup->setEnabled(userMode);
|
||||
myFreqPopup->setEnabled(userMode);
|
||||
myResamplingPopup->setEnabled(userMode);
|
||||
myHeadroomSlider->setEnabled(userMode);
|
||||
myBufferSizeSlider->setEnabled(userMode);
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void AudioDialog::handleCommand(CommandSender* sender, int cmd,
|
||||
int data, int id)
|
||||
|
@ -195,6 +284,10 @@ void AudioDialog::handleCommand(CommandSender* sender, int cmd,
|
|||
handleSoundEnableChange(data == 1);
|
||||
break;
|
||||
|
||||
case kModeChanged:
|
||||
handleModeChange(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
Dialog::handleCommand(sender, cmd, data, 0);
|
||||
break;
|
||||
|
|
|
@ -26,6 +26,7 @@ class SliderWidget;
|
|||
class StaticTextWidget;
|
||||
class CheckboxWidget;
|
||||
class OSystem;
|
||||
class AudioSettings;
|
||||
|
||||
#include "bspf.hxx"
|
||||
|
||||
|
@ -41,17 +42,27 @@ class AudioDialog : public Dialog
|
|||
void setDefaults() override;
|
||||
|
||||
void handleSoundEnableChange(bool active);
|
||||
void handleModeChange(bool active);
|
||||
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
kSoundEnableChanged = 'ADse'
|
||||
kSoundEnableChanged = 'ADse',
|
||||
kModeChanged = 'ADmc'
|
||||
};
|
||||
|
||||
CheckboxWidget* mySoundEnableCheckbox;
|
||||
SliderWidget* myVolumeSlider;
|
||||
PopUpWidget* myModePopup;
|
||||
PopUpWidget* myFragsizePopup;
|
||||
PopUpWidget* myFreqPopup;
|
||||
CheckboxWidget* mySoundEnableCheckbox;
|
||||
PopUpWidget* myResamplingPopup;
|
||||
SliderWidget* myHeadroomSlider;
|
||||
SliderWidget* myBufferSizeSlider;
|
||||
|
||||
private:
|
||||
|
||||
void updatePresetSettings(AudioSettings&);
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
|
|
|
@ -795,7 +795,7 @@ bool Dialog::getResizableBounds(uInt32& w, uInt32& h) const
|
|||
|
||||
if(instance().hasConsole())
|
||||
{
|
||||
ntsc = instance().console().about().InitialFrameRate == "60";
|
||||
ntsc = instance().console().tia().frameLayout() == FrameLayout::ntsc;
|
||||
}
|
||||
|
||||
uInt32 aspect = instance().settings().getInt(ntsc ?"tia.aspectn" : "tia.aspectp");
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
//============================================================================
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "Control.hxx"
|
||||
#include "Dialog.hxx"
|
||||
|
@ -32,6 +34,45 @@
|
|||
#include "TIASurface.hxx"
|
||||
#include "VideoDialog.hxx"
|
||||
|
||||
namespace {
|
||||
// Emulation speed is a positive float that multiplies the framerate. However, the UI controls
|
||||
// adjust speed in terms of a speedup factor (1/10, 1/9 .. 1/2, 1, 2, 3, .., 10). The following
|
||||
// mapping and formatting functions implement this conversion. The speedup factor is represented
|
||||
// by an integer value between -900 and 900 (0 means no speedup).
|
||||
|
||||
constexpr int MAX_SPEED = 900;
|
||||
constexpr int MIN_SPEED = -900;
|
||||
constexpr int SPEED_STEP = 10;
|
||||
|
||||
int mapSpeed(float speed)
|
||||
{
|
||||
speed = abs(speed);
|
||||
|
||||
return BSPF::clamp(
|
||||
static_cast<int>(round(100 * (speed >= 1 ? speed - 1 : -1 / speed + 1))),
|
||||
MIN_SPEED, MAX_SPEED
|
||||
);
|
||||
}
|
||||
|
||||
float unmapSpeed(int speed)
|
||||
{
|
||||
float f_speed = static_cast<float>(speed) / 100;
|
||||
|
||||
return speed < 0 ? -1 / (f_speed - 1) : 1 + f_speed;
|
||||
}
|
||||
|
||||
string formatSpeed(int speed) {
|
||||
stringstream ss;
|
||||
|
||||
ss
|
||||
<< (speed >= 0 ? "x " : "/ ")
|
||||
<< std::setw(4) << std::fixed << std::setprecision(2)
|
||||
<< (1 + static_cast<float>(speed < 0 ? -speed : speed) / 100);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
||||
const GUI::Font& font, int max_w, int max_h)
|
||||
|
@ -121,13 +162,13 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
|||
wid.push_back(myPAspectRatio);
|
||||
ypos += lineHeight + VGAP;
|
||||
|
||||
// Framerate
|
||||
myFrameRate =
|
||||
// Speed
|
||||
mySpeed =
|
||||
new SliderWidget(myTab, font, xpos, ypos-1, swidth, lineHeight,
|
||||
"Frame rate ", lwidth, kFrameRateChanged, fontWidth * 6, "fps");
|
||||
myFrameRate->setMinValue(0); myFrameRate->setMaxValue(900);
|
||||
myFrameRate->setStepValue(10);
|
||||
wid.push_back(myFrameRate);
|
||||
"Speed ", lwidth, kSpeedupChanged, fontWidth * 8, "");
|
||||
mySpeed->setMinValue(MIN_SPEED); mySpeed->setMaxValue(MAX_SPEED);
|
||||
mySpeed->setStepValue(SPEED_STEP);
|
||||
wid.push_back(mySpeed);
|
||||
ypos += lineHeight + VGAP;
|
||||
|
||||
// Use sync to vblank
|
||||
|
@ -141,7 +182,7 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
|
|||
"(*) Requires application restart");
|
||||
|
||||
// Move over to the next column
|
||||
xpos += myFrameRate->getWidth() + 16;
|
||||
xpos += mySpeed->getWidth() + 16;
|
||||
ypos = VBORDER;
|
||||
|
||||
// Fullscreen
|
||||
|
@ -327,12 +368,10 @@ void VideoDialog::loadConfig()
|
|||
myNAspectRatio->setValue(instance().settings().getInt("tia.aspectn"));
|
||||
myPAspectRatio->setValue(instance().settings().getInt("tia.aspectp"));
|
||||
|
||||
// Framerate (0 or -1 means automatic framerate calculation)
|
||||
int rate = instance().settings().getInt("framerate");
|
||||
myFrameRate->setValue(rate < 0 ? 0 : rate);
|
||||
myFrameRate->setValueLabel(rate <= 0 ? "Auto" :
|
||||
instance().settings().getString("framerate"));
|
||||
myFrameRate->setValueUnit(rate <= 0 ? "" : "fps");
|
||||
// Emulation speed
|
||||
int speed = mapSpeed(instance().settings().getFloat("speed"));
|
||||
mySpeed->setValue(speed);
|
||||
mySpeed->setValueLabel(formatSpeed(speed));
|
||||
|
||||
// Fullscreen
|
||||
myFullscreen->setState(instance().settings().getBool("fullscreen"));
|
||||
|
@ -404,15 +443,10 @@ void VideoDialog::saveConfig()
|
|||
instance().settings().setValue("tia.aspectn", myNAspectRatio->getValueLabel());
|
||||
instance().settings().setValue("tia.aspectp", myPAspectRatio->getValueLabel());
|
||||
|
||||
// Framerate
|
||||
int f = myFrameRate->getValue();
|
||||
instance().settings().setValue("framerate", f);
|
||||
if(instance().hasConsole())
|
||||
{
|
||||
// Make sure auto-frame calculation is only enabled when necessary
|
||||
instance().console().tia().enableAutoFrame(f <= 0);
|
||||
instance().console().setFramerate(float(f));
|
||||
}
|
||||
// Speed
|
||||
int speedup = mySpeed->getValue();
|
||||
instance().settings().setValue("speed", unmapSpeed(speedup));
|
||||
if (instance().hasConsole()) instance().console().initializeAudio();
|
||||
|
||||
// Fullscreen
|
||||
instance().settings().setValue("fullscreen", myFullscreen->getState());
|
||||
|
@ -486,7 +520,7 @@ void VideoDialog::setDefaults()
|
|||
myTIAInterpolate->setState(false);
|
||||
myNAspectRatio->setValue(91);
|
||||
myPAspectRatio->setValue(109);
|
||||
myFrameRate->setValue(0);
|
||||
mySpeed->setValue(0);
|
||||
|
||||
myFullscreen->setState(false);
|
||||
//myFullScreenMode->setSelectedIndex(0);
|
||||
|
@ -585,10 +619,8 @@ void VideoDialog::handleCommand(CommandSender* sender, int cmd,
|
|||
setDefaults();
|
||||
break;
|
||||
|
||||
case kFrameRateChanged:
|
||||
if(myFrameRate->getValue() == 0)
|
||||
myFrameRate->setValueLabel("Auto");
|
||||
myFrameRate->setValueUnit(myFrameRate->getValue() == 0 ? "" : "fps");
|
||||
case kSpeedupChanged:
|
||||
mySpeed->setValueLabel(formatSpeed(mySpeed->getValue()));
|
||||
break;
|
||||
|
||||
case kTVModeChanged:
|
||||
|
|
|
@ -58,7 +58,7 @@ class VideoDialog : public Dialog
|
|||
CheckboxWidget* myTIAInterpolate;
|
||||
SliderWidget* myNAspectRatio;
|
||||
SliderWidget* myPAspectRatio;
|
||||
SliderWidget* myFrameRate;
|
||||
SliderWidget* mySpeed;
|
||||
|
||||
CheckboxWidget* myFullscreen;
|
||||
//PopUpWidget* myFullScreenMode;
|
||||
|
@ -99,7 +99,7 @@ class VideoDialog : public Dialog
|
|||
ButtonWidget* myCloneCustom;
|
||||
|
||||
enum {
|
||||
kFrameRateChanged = 'VDfr',
|
||||
kSpeedupChanged = 'VDSp',
|
||||
|
||||
kTVModeChanged = 'VDtv',
|
||||
|
||||
|
|
|
@ -104,7 +104,6 @@
|
|||
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20F9FF08C603EC00A73076 /* TogglePixelWidget.hxx */; };
|
||||
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20FA0108C603EC00A73076 /* ToggleWidget.hxx */; };
|
||||
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D6CC10408C811A600B8F642 /* TiaZoomWidget.hxx */; };
|
||||
2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2DE7242E08CE910900C889A8 /* TIASnd.hxx */; };
|
||||
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D2331900900B5EF00613B1F /* AudioWidget.hxx */; };
|
||||
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF5F096E269100A518FE /* EventMappingWidget.hxx */; };
|
||||
2D91746A09BA90380026E9FF /* InputDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF61096E269100A518FE /* InputDialog.hxx */; };
|
||||
|
@ -202,7 +201,6 @@
|
|||
2D91750309BA90380026E9FF /* TogglePixelWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F9FE08C603EC00A73076 /* TogglePixelWidget.cxx */; };
|
||||
2D91750409BA90380026E9FF /* ToggleWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20FA0008C603EC00A73076 /* ToggleWidget.cxx */; };
|
||||
2D91750609BA90380026E9FF /* TiaZoomWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D6CC10308C811A600B8F642 /* TiaZoomWidget.cxx */; };
|
||||
2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2DE7242D08CE910900C889A8 /* TIASnd.cxx */; };
|
||||
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D23318F0900B5EF00613B1F /* AudioWidget.cxx */; };
|
||||
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF5E096E269100A518FE /* EventMappingWidget.cxx */; };
|
||||
2D91750C09BA90380026E9FF /* InputDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF60096E269100A518FE /* InputDialog.cxx */; };
|
||||
|
@ -511,6 +509,9 @@
|
|||
DCC527D610B9DA19005E1287 /* System.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC527CE10B9DA19005E1287 /* System.cxx */; };
|
||||
DCC527D710B9DA19005E1287 /* System.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527CF10B9DA19005E1287 /* System.hxx */; };
|
||||
DCC527DB10B9DA6A005E1287 /* bspf.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527D810B9DA6A005E1287 /* bspf.hxx */; };
|
||||
DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4AE20A2622500863C59 /* Resampler.hxx */; };
|
||||
DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */; };
|
||||
DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */; };
|
||||
DCCA26B31FA64D5E000EE4D8 /* AbstractFrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */; };
|
||||
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */; };
|
||||
DCCF47DE14B60DEE00814FAB /* ControllerWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */; };
|
||||
|
@ -564,6 +565,8 @@
|
|||
DCDE17FB17724E5D00EB1AC6 /* ConfigPathDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */; };
|
||||
DCDE17FC17724E5D00EB1AC6 /* SnapshotDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */; };
|
||||
DCDE17FD17724E5D00EB1AC6 /* SnapshotDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */; };
|
||||
DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */; };
|
||||
DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDFF08020B781B0001227C0 /* DispatchResult.hxx */; };
|
||||
DCE395DB16CB0B2B008DB1E5 /* FSNodePOSIX.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */; };
|
||||
DCE395EF16CB0B5F008DB1E5 /* FSNodeFactory.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */; };
|
||||
DCE395F016CB0B5F008DB1E5 /* FSNodeZIP.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */; };
|
||||
|
@ -602,7 +605,6 @@
|
|||
DCF3A6FD1DFC75E3008A8AF3 /* Playfield.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */; };
|
||||
DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */; };
|
||||
DCF3A6FF1DFC75E3008A8AF3 /* TIA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */; };
|
||||
DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */; };
|
||||
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467B40F93993B00B25D7A /* SoundNull.hxx */; };
|
||||
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467BC0F9399F500B25D7A /* Version.hxx */; };
|
||||
DCF467C20F939A1400B25D7A /* CartEF.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF467BE0F939A1400B25D7A /* CartEF.cxx */; };
|
||||
|
@ -615,6 +617,8 @@
|
|||
DCF7B0DF10A762FC007A2870 /* CartFA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF7B0DB10A762FC007A2870 /* CartFA.cxx */; };
|
||||
DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF7B0DC10A762FC007A2870 /* CartFA.hxx */; };
|
||||
DCFB9FAC1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */; };
|
||||
DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */; };
|
||||
DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */; };
|
||||
DCFF14CD18B0260300A20364 /* EventHandlerSDL2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */; };
|
||||
DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; };
|
||||
DCFFE59D12100E1400DFA000 /* ComboDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */; };
|
||||
|
@ -625,8 +629,20 @@
|
|||
E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E091F93E915003DDD52 /* JitterEmulation.cxx */; };
|
||||
E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; };
|
||||
E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; };
|
||||
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */; };
|
||||
E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */; };
|
||||
E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; };
|
||||
E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */; };
|
||||
E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4139201E901C004A3391 /* AudioQueue.hxx */; };
|
||||
E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413A201E901D004A3391 /* AudioQueue.cxx */; };
|
||||
E09F4141201E9050004A3391 /* Audio.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F413D201E904F004A3391 /* Audio.hxx */; };
|
||||
E09F4142201E9050004A3391 /* Audio.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413E201E904F004A3391 /* Audio.cxx */; };
|
||||
E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413F201E904F004A3391 /* AudioChannel.cxx */; };
|
||||
E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4140201E904F004A3391 /* AudioChannel.hxx */; };
|
||||
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */; };
|
||||
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */; };
|
||||
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */; };
|
||||
E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
|
@ -874,8 +890,6 @@
|
|||
2DE2DF8D0627AE34006BEC99 /* Sound.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Sound.hxx; sourceTree = "<group>"; };
|
||||
2DE2DF8E0627AE34006BEC99 /* Switches.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Switches.cxx; sourceTree = "<group>"; };
|
||||
2DE2DF8F0627AE34006BEC99 /* Switches.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Switches.hxx; sourceTree = "<group>"; };
|
||||
2DE7242D08CE910900C889A8 /* TIASnd.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TIASnd.cxx; sourceTree = "<group>"; };
|
||||
2DE7242E08CE910900C889A8 /* TIASnd.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = TIASnd.hxx; sourceTree = "<group>"; };
|
||||
2DEB3D4C0629BD24007EBBD3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
|
||||
2DEF21F808BC033500B246B4 /* CheckListWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CheckListWidget.cxx; sourceTree = "<group>"; };
|
||||
2DEF21F908BC033500B246B4 /* CheckListWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CheckListWidget.hxx; sourceTree = "<group>"; };
|
||||
|
@ -1187,6 +1201,9 @@
|
|||
DCC527CE10B9DA19005E1287 /* System.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = System.cxx; sourceTree = "<group>"; };
|
||||
DCC527CF10B9DA19005E1287 /* System.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = System.hxx; sourceTree = "<group>"; };
|
||||
DCC527D810B9DA6A005E1287 /* bspf.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = bspf.hxx; sourceTree = "<group>"; };
|
||||
DCC6A4AE20A2622500863C59 /* Resampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Resampler.hxx; path = audio/Resampler.hxx; sourceTree = "<group>"; };
|
||||
DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SimpleResampler.cxx; path = audio/SimpleResampler.cxx; sourceTree = "<group>"; };
|
||||
DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SimpleResampler.hxx; path = audio/SimpleResampler.hxx; sourceTree = "<group>"; };
|
||||
DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AbstractFrameManager.hxx; sourceTree = "<group>"; };
|
||||
DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = "<group>"; };
|
||||
DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ControllerWidget.hxx; sourceTree = "<group>"; };
|
||||
|
@ -1240,6 +1257,8 @@
|
|||
DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfigPathDialog.hxx; sourceTree = "<group>"; };
|
||||
DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SnapshotDialog.cxx; sourceTree = "<group>"; };
|
||||
DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SnapshotDialog.hxx; sourceTree = "<group>"; };
|
||||
DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DispatchResult.cxx; sourceTree = "<group>"; };
|
||||
DCDFF08020B781B0001227C0 /* DispatchResult.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DispatchResult.hxx; sourceTree = "<group>"; };
|
||||
DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodePOSIX.hxx; sourceTree = "<group>"; };
|
||||
DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodeFactory.hxx; sourceTree = "<group>"; };
|
||||
DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FSNodeZIP.cxx; sourceTree = "<group>"; };
|
||||
|
@ -1278,7 +1297,6 @@
|
|||
DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Playfield.hxx; sourceTree = "<group>"; };
|
||||
DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cxx; sourceTree = "<group>"; };
|
||||
DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hxx; sourceTree = "<group>"; };
|
||||
DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIATypes.hxx; sourceTree = "<group>"; };
|
||||
DCF467B40F93993B00B25D7A /* SoundNull.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundNull.hxx; sourceTree = "<group>"; };
|
||||
DCF467BC0F9399F500B25D7A /* Version.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Version.hxx; sourceTree = "<group>"; };
|
||||
DCF467BE0F939A1400B25D7A /* CartEF.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartEF.cxx; sourceTree = "<group>"; };
|
||||
|
@ -1291,6 +1309,8 @@
|
|||
DCF7B0DB10A762FC007A2870 /* CartFA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartFA.cxx; sourceTree = "<group>"; };
|
||||
DCF7B0DC10A762FC007A2870 /* CartFA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartFA.hxx; sourceTree = "<group>"; };
|
||||
DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueIteratorImpl.hxx; sourceTree = "<group>"; };
|
||||
DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationWorker.cxx; sourceTree = "<group>"; };
|
||||
DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationWorker.hxx; sourceTree = "<group>"; };
|
||||
DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventHandlerSDL2.cxx; sourceTree = "<group>"; };
|
||||
DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EventHandlerSDL2.hxx; sourceTree = "<group>"; };
|
||||
DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComboDialog.cxx; sourceTree = "<group>"; };
|
||||
|
@ -1301,6 +1321,18 @@
|
|||
E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = "<group>"; };
|
||||
E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = "<group>"; };
|
||||
E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = "<group>"; };
|
||||
E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationTiming.cxx; sourceTree = "<group>"; };
|
||||
E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationTiming.hxx; sourceTree = "<group>"; };
|
||||
E09F4139201E901C004A3391 /* AudioQueue.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioQueue.hxx; sourceTree = "<group>"; };
|
||||
E09F413A201E901D004A3391 /* AudioQueue.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioQueue.cxx; sourceTree = "<group>"; };
|
||||
E09F413D201E904F004A3391 /* Audio.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Audio.hxx; sourceTree = "<group>"; };
|
||||
E09F413E201E904F004A3391 /* Audio.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cxx; sourceTree = "<group>"; };
|
||||
E09F413F201E904F004A3391 /* AudioChannel.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioChannel.cxx; sourceTree = "<group>"; };
|
||||
E09F4140201E904F004A3391 /* AudioChannel.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioChannel.hxx; sourceTree = "<group>"; };
|
||||
E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = LanczosResampler.hxx; path = audio/LanczosResampler.hxx; sourceTree = "<group>"; };
|
||||
E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LanczosResampler.cxx; path = audio/LanczosResampler.cxx; sourceTree = "<group>"; };
|
||||
E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ConvolutionBuffer.hxx; path = audio/ConvolutionBuffer.hxx; sourceTree = "<group>"; };
|
||||
E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ConvolutionBuffer.cxx; path = audio/ConvolutionBuffer.cxx; sourceTree = "<group>"; };
|
||||
E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = "<group>"; };
|
||||
E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = "<group>"; };
|
||||
F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -1578,6 +1610,9 @@
|
|||
2D6050C5089876F300C6DE89 /* common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCC6A4AD20A2620D00863C59 /* audio */,
|
||||
E09F413A201E901D004A3391 /* AudioQueue.cxx */,
|
||||
E09F4139201E901C004A3391 /* AudioQueue.hxx */,
|
||||
DC79F81017A88D9E00288B91 /* Base.cxx */,
|
||||
DC79F81117A88D9E00288B91 /* Base.hxx */,
|
||||
DCC527D810B9DA6A005E1287 /* bspf.hxx */,
|
||||
|
@ -1756,8 +1791,14 @@
|
|||
2DE2DF3B0627AE07006BEC99 /* Control.hxx */,
|
||||
DC932D3F0F278A5200FEFEFC /* DefProps.hxx */,
|
||||
DCC527C910B9DA19005E1287 /* Device.hxx */,
|
||||
DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */,
|
||||
DCDFF08020B781B0001227C0 /* DispatchResult.hxx */,
|
||||
2DE2DF3E0627AE07006BEC99 /* Driving.cxx */,
|
||||
2DE2DF3F0627AE07006BEC99 /* Driving.hxx */,
|
||||
E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */,
|
||||
E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */,
|
||||
DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */,
|
||||
DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */,
|
||||
2DE2DF410627AE07006BEC99 /* Event.hxx */,
|
||||
2D733D6E062895B2006265D9 /* EventHandler.cxx */,
|
||||
2D733D6F062895B2006265D9 /* EventHandler.hxx */,
|
||||
|
@ -1815,11 +1856,8 @@
|
|||
DCD2839612E39F1200A808DC /* Thumbulator.cxx */,
|
||||
DCD2839712E39F1200A808DC /* Thumbulator.hxx */,
|
||||
DCE903E31DF5DCD10080A7F3 /* tia */,
|
||||
2DE7242D08CE910900C889A8 /* TIASnd.cxx */,
|
||||
2DE7242E08CE910900C889A8 /* TIASnd.hxx */,
|
||||
DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */,
|
||||
DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */,
|
||||
DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */,
|
||||
DC1B2EC21E50036100F62837 /* TrakBall.hxx */,
|
||||
);
|
||||
path = emucore;
|
||||
|
@ -2020,6 +2058,20 @@
|
|||
path = tv_filters;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCC6A4AD20A2620D00863C59 /* audio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */,
|
||||
E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */,
|
||||
E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */,
|
||||
E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */,
|
||||
DCC6A4AE20A2622500863C59 /* Resampler.hxx */,
|
||||
DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */,
|
||||
DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */,
|
||||
);
|
||||
name = audio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCCC0C9109C3541E0088BFF1 /* cheat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2070,6 +2122,10 @@
|
|||
DCE903E31DF5DCD10080A7F3 /* tia */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E09F413E201E904F004A3391 /* Audio.cxx */,
|
||||
E09F413D201E904F004A3391 /* Audio.hxx */,
|
||||
E09F413F201E904F004A3391 /* AudioChannel.cxx */,
|
||||
E09F4140201E904F004A3391 /* AudioChannel.hxx */,
|
||||
DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */,
|
||||
DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */,
|
||||
DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */,
|
||||
|
@ -2124,11 +2180,13 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2D9173CB09BA90380026E9FF /* SDLMain.h in Headers */,
|
||||
E09F4141201E9050004A3391 /* Audio.hxx in Headers */,
|
||||
2D9173CC09BA90380026E9FF /* Booster.hxx in Headers */,
|
||||
2D9173CD09BA90380026E9FF /* Cart.hxx in Headers */,
|
||||
2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */,
|
||||
2D9173CF09BA90380026E9FF /* Cart3F.hxx in Headers */,
|
||||
DC3EE86D1E2C0E6D00905161 /* zlib.h in Headers */,
|
||||
E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */,
|
||||
DC3EE86A1E2C0E6D00905161 /* trees.h in Headers */,
|
||||
2D9173D009BA90380026E9FF /* Cart4K.hxx in Headers */,
|
||||
2D9173D109BA90380026E9FF /* CartAR.hxx in Headers */,
|
||||
|
@ -2171,6 +2229,7 @@
|
|||
2D9173EE09BA90380026E9FF /* Serializer.hxx in Headers */,
|
||||
2D9173EF09BA90380026E9FF /* Sound.hxx in Headers */,
|
||||
2D9173F009BA90380026E9FF /* Switches.hxx in Headers */,
|
||||
E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */,
|
||||
2D9173F909BA90380026E9FF /* EventHandler.hxx in Headers */,
|
||||
2D9173FA09BA90380026E9FF /* FrameBuffer.hxx in Headers */,
|
||||
2D9173FB09BA90380026E9FF /* Settings.hxx in Headers */,
|
||||
|
@ -2186,6 +2245,7 @@
|
|||
2D91740309BA90380026E9FF /* Command.hxx in Headers */,
|
||||
DC3EE85B1E2C0E6D00905161 /* deflate.h in Headers */,
|
||||
2D91740409BA90380026E9FF /* Dialog.hxx in Headers */,
|
||||
E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */,
|
||||
2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */,
|
||||
DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */,
|
||||
2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */,
|
||||
|
@ -2223,6 +2283,7 @@
|
|||
DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */,
|
||||
2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */,
|
||||
DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */,
|
||||
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */,
|
||||
2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */,
|
||||
2D91742909BA90380026E9FF /* TIADebug.hxx in Headers */,
|
||||
2D91742A09BA90380026E9FF /* YaccParser.hxx in Headers */,
|
||||
|
@ -2246,6 +2307,7 @@
|
|||
2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */,
|
||||
2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */,
|
||||
2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */,
|
||||
DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */,
|
||||
DCF3A6EC1DFC75E3008A8AF3 /* DelayQueue.hxx in Headers */,
|
||||
DCA078341F8C1B04008EFEE5 /* LinkedObjectPool.hxx in Headers */,
|
||||
2D91745809BA90380026E9FF /* DebuggerDialog.hxx in Headers */,
|
||||
|
@ -2267,7 +2329,6 @@
|
|||
2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */,
|
||||
2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */,
|
||||
2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */,
|
||||
2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */,
|
||||
DC1BC6672066B4390076F74A /* PKeyboardHandler.hxx in Headers */,
|
||||
2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */,
|
||||
2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */,
|
||||
|
@ -2285,6 +2346,7 @@
|
|||
DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */,
|
||||
DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */,
|
||||
DCE3BBFA0C95CEDC00A671DF /* RomInfoWidget.hxx in Headers */,
|
||||
DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */,
|
||||
DC6DC91F205DB879004A5FC3 /* PhysicalJoystick.hxx in Headers */,
|
||||
DC0984860D3985160073C852 /* CartSB.hxx in Headers */,
|
||||
DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */,
|
||||
|
@ -2298,6 +2360,7 @@
|
|||
DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */,
|
||||
DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */,
|
||||
DC0DF86A0F0DAAF500B0F1F3 /* GlobalPropsDialog.hxx in Headers */,
|
||||
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */,
|
||||
DC5D2C520F117CFD004D1660 /* Rect.hxx in Headers */,
|
||||
DC71EAA81FDA070D008827CB /* CartMNetworkWidget.hxx in Headers */,
|
||||
DC5D2C530F117CFD004D1660 /* StellaFont.hxx in Headers */,
|
||||
|
@ -2309,7 +2372,6 @@
|
|||
DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */,
|
||||
DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */,
|
||||
DC9EA8880F729A36000452B5 /* KidVid.hxx in Headers */,
|
||||
DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */,
|
||||
DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */,
|
||||
DCBDDE9F1D6A5F2F009DF1E9 /* Cart3EPlus.hxx in Headers */,
|
||||
DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */,
|
||||
|
@ -2350,6 +2412,7 @@
|
|||
DC74D6A2138D4D7E00F05C5C /* StringParser.hxx in Headers */,
|
||||
DC2874071F8F2278004BF21A /* TrapArray.hxx in Headers */,
|
||||
DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */,
|
||||
DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */,
|
||||
DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */,
|
||||
DC8C1BAE14B25DE7006440EE /* CartCM.hxx in Headers */,
|
||||
DCF3A6FB1DFC75E3008A8AF3 /* Player.hxx in Headers */,
|
||||
|
@ -2386,6 +2449,7 @@
|
|||
DC6DC921205DB879004A5FC3 /* PJoystickHandler.hxx in Headers */,
|
||||
DCAAE5DF1715887B0080BB82 /* CartEFSCWidget.hxx in Headers */,
|
||||
DCAAE5E11715887B0080BB82 /* CartEFWidget.hxx in Headers */,
|
||||
DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */,
|
||||
DCAAE5E31715887B0080BB82 /* CartF0Widget.hxx in Headers */,
|
||||
DCAAE5E51715887B0080BB82 /* CartF4SCWidget.hxx in Headers */,
|
||||
DCAAE5E71715887B0080BB82 /* CartF4Widget.hxx in Headers */,
|
||||
|
@ -2551,6 +2615,7 @@
|
|||
DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */,
|
||||
2D91747609BA90380026E9FF /* Cart.cxx in Sources */,
|
||||
2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */,
|
||||
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */,
|
||||
2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */,
|
||||
2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */,
|
||||
2D91747A09BA90380026E9FF /* CartAR.cxx in Sources */,
|
||||
|
@ -2559,9 +2624,11 @@
|
|||
DCF3A6FC1DFC75E3008A8AF3 /* Playfield.cxx in Sources */,
|
||||
2D91747C09BA90380026E9FF /* CartDPC.cxx in Sources */,
|
||||
2D91747D09BA90380026E9FF /* CartE0.cxx in Sources */,
|
||||
E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */,
|
||||
2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */,
|
||||
DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */,
|
||||
2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */,
|
||||
DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */,
|
||||
2D91748009BA90380026E9FF /* CartF4SC.cxx in Sources */,
|
||||
2D91748109BA90380026E9FF /* CartF6.cxx in Sources */,
|
||||
2D91748209BA90380026E9FF /* CartF6SC.cxx in Sources */,
|
||||
|
@ -2578,6 +2645,7 @@
|
|||
2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */,
|
||||
2D91749009BA90380026E9FF /* M6532.cxx in Sources */,
|
||||
2D91749109BA90380026E9FF /* MD5.cxx in Sources */,
|
||||
E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */,
|
||||
DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */,
|
||||
2D91749309BA90380026E9FF /* Paddles.cxx in Sources */,
|
||||
2D91749409BA90380026E9FF /* Props.cxx in Sources */,
|
||||
|
@ -2650,6 +2718,7 @@
|
|||
2D9174FA09BA90380026E9FF /* DebuggerDialog.cxx in Sources */,
|
||||
DCF3A6E91DFC75E3008A8AF3 /* Ball.cxx in Sources */,
|
||||
2D9174FB09BA90380026E9FF /* PromptWidget.cxx in Sources */,
|
||||
DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */,
|
||||
2D9174FC09BA90380026E9FF /* RamWidget.cxx in Sources */,
|
||||
DC2AADAE194F389C0026C7A4 /* CartDASH.cxx in Sources */,
|
||||
2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */,
|
||||
|
@ -2668,7 +2737,7 @@
|
|||
DC71EA9D1FDA06D2008827CB /* CartE78K.cxx in Sources */,
|
||||
DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */,
|
||||
DCDDEAC41F5DBF0400C67366 /* RewindManager.cxx in Sources */,
|
||||
2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */,
|
||||
E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */,
|
||||
DC71EA9F1FDA06D2008827CB /* CartMNetwork.cxx in Sources */,
|
||||
2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */,
|
||||
2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */,
|
||||
|
@ -2753,6 +2822,7 @@
|
|||
DC8C1BAD14B25DE7006440EE /* CartCM.cxx in Sources */,
|
||||
DCDDEAC61F5DBF0400C67366 /* StateManager.cxx in Sources */,
|
||||
DC8C1BAF14B25DE7006440EE /* CompuMate.cxx in Sources */,
|
||||
E09F4142201E9050004A3391 /* Audio.cxx in Sources */,
|
||||
DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */,
|
||||
DCCF47DF14B60DEE00814FAB /* JoystickWidget.cxx in Sources */,
|
||||
DCCF49B714B7544A00814FAB /* PaddleWidget.cxx in Sources */,
|
||||
|
@ -2774,6 +2844,7 @@
|
|||
DC3EE8601E2C0E6D00905161 /* gzwrite.c in Sources */,
|
||||
DCAAE5D71715887B0080BB82 /* Cart4KWidget.cxx in Sources */,
|
||||
DCDA03B01A2009BB00711920 /* CartWD.cxx in Sources */,
|
||||
DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */,
|
||||
DCAAE5D91715887B0080BB82 /* Cart0840Widget.cxx in Sources */,
|
||||
DCAAE5DB1715887B0080BB82 /* CartCVWidget.cxx in Sources */,
|
||||
DCAAE5DE1715887B0080BB82 /* CartEFSCWidget.cxx in Sources */,
|
||||
|
@ -2802,6 +2873,7 @@
|
|||
DC676A4D1729A0B000E4E73D /* CartDPCWidget.cxx in Sources */,
|
||||
DC676A4F1729A0B000E4E73D /* CartE0Widget.cxx in Sources */,
|
||||
DC676A511729A0B000E4E73D /* CartE7Widget.cxx in Sources */,
|
||||
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */,
|
||||
DC676A531729A0B000E4E73D /* CartFA2Widget.cxx in Sources */,
|
||||
DC676A551729A0B000E4E73D /* CartFEWidget.cxx in Sources */,
|
||||
DC676A591729A0B000E4E73D /* CartSBWidget.cxx in Sources */,
|
||||
|
|
|
@ -20,6 +20,4 @@
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SettingsWINDOWS::SettingsWINDOWS(OSystem& osystem)
|
||||
: Settings(osystem)
|
||||
{
|
||||
setInternal("fragsize", "1024");
|
||||
}
|
||||
{}
|
||||
|
|
|
@ -228,6 +228,10 @@
|
|||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common\AudioQueue.cxx" />
|
||||
<ClCompile Include="..\common\audio\ConvolutionBuffer.cxx" />
|
||||
<ClCompile Include="..\common\audio\LanczosResampler.cxx" />
|
||||
<ClCompile Include="..\common\audio\SimpleResampler.cxx" />
|
||||
<ClCompile Include="..\common\Base.cxx" />
|
||||
<ClCompile Include="..\common\EventHandlerSDL2.cxx" />
|
||||
<ClCompile Include="..\common\FBSurfaceSDL2.cxx" />
|
||||
|
@ -323,10 +327,15 @@
|
|||
<ClCompile Include="..\emucore\CartE78K.cxx" />
|
||||
<ClCompile Include="..\emucore\CartWD.cxx" />
|
||||
<ClCompile Include="..\emucore\CompuMate.cxx" />
|
||||
<ClCompile Include="..\emucore\DispatchResult.cxx" />
|
||||
<ClCompile Include="..\emucore\EmulationTiming.cxx" />
|
||||
<ClCompile Include="..\emucore\EmulationWorker.cxx" />
|
||||
<ClCompile Include="..\emucore\FBSurface.cxx" />
|
||||
<ClCompile Include="..\emucore\MindLink.cxx" />
|
||||
<ClCompile Include="..\emucore\PointingDevice.cxx" />
|
||||
<ClCompile Include="..\emucore\TIASurface.cxx" />
|
||||
<ClCompile Include="..\emucore\tia\Audio.cxx" />
|
||||
<ClCompile Include="..\emucore\tia\AudioChannel.cxx" />
|
||||
<ClCompile Include="..\emucore\tia\Background.cxx" />
|
||||
<ClCompile Include="..\emucore\tia\Ball.cxx" />
|
||||
<ClCompile Include="..\emucore\tia\DrawCounterDecodes.cxx" />
|
||||
|
@ -410,7 +419,6 @@
|
|||
<ClCompile Include="..\emucore\Switches.cxx" />
|
||||
<ClCompile Include="..\emucore\System.cxx" />
|
||||
<ClCompile Include="..\emucore\Thumbulator.cxx" />
|
||||
<ClCompile Include="..\emucore\TIASnd.cxx" />
|
||||
<ClCompile Include="..\cheat\BankRomCheat.cxx" />
|
||||
<ClCompile Include="..\cheat\CheatCodeDialog.cxx" />
|
||||
<ClCompile Include="..\cheat\CheatManager.cxx" />
|
||||
|
@ -511,6 +519,11 @@
|
|||
<ClCompile Include="..\libpng\pngwutil.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\AudioQueue.hxx" />
|
||||
<ClInclude Include="..\common\audio\ConvolutionBuffer.hxx" />
|
||||
<ClInclude Include="..\common\audio\LanczosResampler.hxx" />
|
||||
<ClInclude Include="..\common\audio\Resampler.hxx" />
|
||||
<ClInclude Include="..\common\audio\SimpleResampler.hxx" />
|
||||
<ClInclude Include="..\common\Base.hxx" />
|
||||
<ClInclude Include="..\common\bspf.hxx" />
|
||||
<ClInclude Include="..\common\EventHandlerSDL2.hxx" />
|
||||
|
@ -620,12 +633,17 @@
|
|||
<ClInclude Include="..\emucore\CartE78K.hxx" />
|
||||
<ClInclude Include="..\emucore\CartWD.hxx" />
|
||||
<ClInclude Include="..\emucore\CompuMate.hxx" />
|
||||
<ClInclude Include="..\emucore\DispatchResult.hxx" />
|
||||
<ClInclude Include="..\emucore\EmulationTiming.hxx" />
|
||||
<ClInclude Include="..\emucore\EmulationWorker.hxx" />
|
||||
<ClInclude Include="..\emucore\EventHandlerConstants.hxx" />
|
||||
<ClInclude Include="..\emucore\FBSurface.hxx" />
|
||||
<ClInclude Include="..\emucore\FrameBufferConstants.hxx" />
|
||||
<ClInclude Include="..\emucore\MindLink.hxx" />
|
||||
<ClInclude Include="..\emucore\PointingDevice.hxx" />
|
||||
<ClInclude Include="..\emucore\TIASurface.hxx" />
|
||||
<ClInclude Include="..\emucore\tia\Audio.hxx" />
|
||||
<ClInclude Include="..\emucore\tia\AudioChannel.hxx" />
|
||||
<ClInclude Include="..\emucore\tia\Background.hxx" />
|
||||
<ClInclude Include="..\emucore\tia\Ball.hxx" />
|
||||
<ClInclude Include="..\emucore\tia\DelayQueue.hxx" />
|
||||
|
@ -733,7 +751,6 @@
|
|||
<ClInclude Include="..\emucore\Switches.hxx" />
|
||||
<ClInclude Include="..\emucore\System.hxx" />
|
||||
<ClInclude Include="..\emucore\Thumbulator.hxx" />
|
||||
<ClInclude Include="..\emucore\TIASnd.hxx" />
|
||||
<ClInclude Include="..\debugger\gui\AudioWidget.hxx" />
|
||||
<ClInclude Include="..\debugger\CartDebug.hxx" />
|
||||
<ClInclude Include="..\debugger\CpuDebug.hxx" />
|
||||
|
|
|
@ -55,6 +55,12 @@
|
|||
<Filter Include="Source Files\emucore\tia">
|
||||
<UniqueIdentifier>{ffa3642d-aa8a-43a5-8ac5-acd8878dd091}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\audio">
|
||||
<UniqueIdentifier>{49d8ea64-20c1-45f1-9dc9-b39c17d7cabd}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\audio">
|
||||
<UniqueIdentifier>{000e4a6b-8cd6-43db-8253-8255c7efa706}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common\FrameBufferSDL2.cxx">
|
||||
|
@ -237,9 +243,6 @@
|
|||
<ClCompile Include="..\emucore\Thumbulator.cxx">
|
||||
<Filter>Source Files\emucore</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\TIASnd.cxx">
|
||||
<Filter>Source Files\emucore</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\cheat\BankRomCheat.cxx">
|
||||
<Filter>Source Files\cheat</Filter>
|
||||
</ClCompile>
|
||||
|
@ -900,6 +903,33 @@
|
|||
<ClCompile Include="..\common\PKeyboardHandler.cxx">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\tia\Audio.cxx">
|
||||
<Filter>Source Files\emucore\tia</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\tia\AudioChannel.cxx">
|
||||
<Filter>Source Files\emucore\tia</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\common\AudioQueue.cxx">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\EmulationTiming.cxx">
|
||||
<Filter>Source Files\emucore</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\common\audio\SimpleResampler.cxx">
|
||||
<Filter>Source Files\audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\common\audio\ConvolutionBuffer.cxx">
|
||||
<Filter>Source Files\audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\common\audio\LanczosResampler.cxx">
|
||||
<Filter>Source Files\audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\DispatchResult.cxx">
|
||||
<Filter>Source Files\emucore</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\emucore\EmulationWorker.cxx">
|
||||
<Filter>Source Files\emucore</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\bspf.hxx">
|
||||
|
@ -1112,9 +1142,6 @@
|
|||
<ClInclude Include="..\emucore\Thumbulator.hxx">
|
||||
<Filter>Header Files\emucore</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\TIASnd.hxx">
|
||||
<Filter>Header Files\emucore</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\debugger\gui\AudioWidget.hxx">
|
||||
<Filter>Header Files\debugger</Filter>
|
||||
</ClInclude>
|
||||
|
@ -1844,6 +1871,36 @@
|
|||
<ClInclude Include="..\common\PKeyboardHandler.hxx">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\tia\Audio.hxx">
|
||||
<Filter>Header Files\emucore\tia</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\tia\AudioChannel.hxx">
|
||||
<Filter>Header Files\emucore\tia</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\common\AudioQueue.hxx">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\EmulationTiming.hxx">
|
||||
<Filter>Header Files\emucore</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\common\audio\Resampler.hxx">
|
||||
<Filter>Header Files\audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\common\audio\SimpleResampler.hxx">
|
||||
<Filter>Header Files\audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\common\audio\ConvolutionBuffer.hxx">
|
||||
<Filter>Header Files\audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\common\audio\LanczosResampler.hxx">
|
||||
<Filter>Header Files\audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\DispatchResult.hxx">
|
||||
<Filter>Header Files\emucore</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\emucore\EmulationWorker.hxx">
|
||||
<Filter>Header Files\emucore</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="stella.ico">
|
||||
|
|
Loading…
Reference in New Issue