Merge branch 'audio2'
This commit is contained in:
commit
342697e983
20 changed files with 466 additions and 37 deletions
4
linux.md
4
linux.md
|
@ -24,7 +24,6 @@ been modified, mostly for
|
|||
|
||||
* header files issues
|
||||
* const char *
|
||||
* STL constructor issues (see Disk_t and HDD)
|
||||
* exclude some Windows heavy blocks (source/MouseInterface.cpp)
|
||||
|
||||
## What works
|
||||
|
@ -41,7 +40,6 @@ Some features totally ignored:
|
|||
* NSTC colors
|
||||
* ethernet
|
||||
* serial port
|
||||
* sound
|
||||
* debugger
|
||||
* speech
|
||||
|
||||
|
@ -77,7 +75,9 @@ This is based on Qt, currently tested with 5.10
|
|||
* joystick: it uses QtGamepad (correct names will only be displayed with 5.11)
|
||||
* emulator runs in the main UI thread
|
||||
* Qt timers are very coarse: the emulator needs to dynamically adapt the cycles to execute
|
||||
* the app runs at 60FPS with correction for uneven timer deltas.
|
||||
* full speed when disk spins execute up to 5 ms real wall clock of emulator code (then returns to Qt)
|
||||
* (standard) audio is supported and there are a few configuration options to tune the latency (default very conservative 200ms)
|
||||
|
||||
## Build
|
||||
|
||||
|
|
|
@ -17,18 +17,21 @@ add_executable(qapple
|
|||
video.cpp
|
||||
settings.cpp
|
||||
configuration.cpp
|
||||
audiogenerator.cpp
|
||||
loggingcategory.cpp
|
||||
|
||||
commands.cpp
|
||||
chunks.cpp
|
||||
qhexedit.cpp
|
||||
qhexedit2/commands.cpp
|
||||
qhexedit2/chunks.cpp
|
||||
qhexedit2/qhexedit.cpp
|
||||
)
|
||||
|
||||
find_package(Qt5 REQUIRED
|
||||
COMPONENTS Widgets Gamepad
|
||||
COMPONENTS Widgets Gamepad Multimedia
|
||||
)
|
||||
|
||||
target_link_libraries(qapple
|
||||
Qt5::Widgets
|
||||
Qt5::Gamepad
|
||||
Qt5::Multimedia
|
||||
appleii
|
||||
)
|
||||
|
|
259
source/frontends/qapple/audiogenerator.cpp
Normal file
259
source/frontends/qapple/audiogenerator.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
#include "StdAfx.h"
|
||||
|
||||
#include "audiogenerator.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "CPU.h"
|
||||
#include "Applewin.h"
|
||||
#include "Memory.h"
|
||||
#include "loggingcategory.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
// Speaker
|
||||
|
||||
BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
|
||||
{
|
||||
CpuCalcCycles(uExecutedCycles);
|
||||
|
||||
Q_UNUSED(pc)
|
||||
Q_UNUSED(addr)
|
||||
Q_UNUSED(bWrite)
|
||||
Q_UNUSED(d)
|
||||
|
||||
AudioGenerator::instance().toggle();
|
||||
|
||||
return MemReadFloatingBus(uExecutedCycles);
|
||||
}
|
||||
|
||||
AudioGenerator & AudioGenerator::instance()
|
||||
{
|
||||
static std::shared_ptr<AudioGenerator> audioGenerator(new AudioGenerator());
|
||||
return *audioGenerator;
|
||||
}
|
||||
|
||||
AudioGenerator::AudioGenerator()
|
||||
{
|
||||
myDevice = nullptr;
|
||||
myInitialSilence = 200;
|
||||
mySilenceDelay = 10000;
|
||||
myVolume = 0x0fff;
|
||||
|
||||
QAudioFormat audioFormat;
|
||||
audioFormat.setSampleRate(44100);
|
||||
audioFormat.setChannelCount(1);
|
||||
audioFormat.setSampleSize(sizeof(audio_t) * 8);
|
||||
audioFormat.setCodec(QString::fromUtf8("audio/pcm"));
|
||||
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
audioFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
|
||||
myAudioOutput.reset(new QAudioOutput(audioFormat));
|
||||
myAudioFormat = myAudioOutput->format();
|
||||
}
|
||||
|
||||
QAudioOutput * AudioGenerator::getAudioOutput()
|
||||
{
|
||||
return myAudioOutput.get();
|
||||
}
|
||||
|
||||
void AudioGenerator::getOptions(qint32 & initialSilence, qint32 & silenceDelay, qint32 & volume) const
|
||||
{
|
||||
initialSilence = myInitialSilence;
|
||||
silenceDelay = mySilenceDelay;
|
||||
volume = myVolume;
|
||||
}
|
||||
|
||||
void AudioGenerator::setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume)
|
||||
{
|
||||
myInitialSilence = std::max(0, initialSilence);
|
||||
mySilenceDelay = std::max(0, silenceDelay);
|
||||
myVolume = std::max(0, volume);
|
||||
}
|
||||
|
||||
void AudioGenerator::stateChanged(QAudio::State state)
|
||||
{
|
||||
qDebug(appleAudio) << "Changed state: state =" << state << ", error =" << myAudioOutput->error() << ", free =" << myAudioOutput->bytesFree() << "bytes";
|
||||
}
|
||||
|
||||
qint64 AudioGenerator::toFrameTime(qint64 cpuCycples)
|
||||
{
|
||||
const double CLKS_PER_SEC = g_fCurrentCLK6502;
|
||||
qint64 timeInFrames = (cpuCycples - myStartCPUCycles) * myAudioFormat.sampleRate() / CLKS_PER_SEC;
|
||||
return timeInFrames;
|
||||
}
|
||||
|
||||
void AudioGenerator::toggle()
|
||||
{
|
||||
const qint64 timeInFrames = toFrameTime(g_nCumulativeCycles);
|
||||
myTicks.push(timeInFrames);
|
||||
}
|
||||
|
||||
bool AudioGenerator::isRunning()
|
||||
{
|
||||
QAudio::State state = myAudioOutput->state();
|
||||
QAudio::Error error = myAudioOutput->error();
|
||||
if (state == QAudio::ActiveState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (state == QAudio::IdleState && error == QAudio::NoError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioGenerator::start()
|
||||
{
|
||||
if (isRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug(appleAudio) << "Restarting the AudioGenerator";
|
||||
|
||||
// restart as we are either starting or recovering from underrun
|
||||
myDevice = myAudioOutput->start();
|
||||
|
||||
const int bytesSize = myAudioOutput->bufferSize();
|
||||
const qint32 frameSize = myAudioFormat.framesForBytes(bytesSize);
|
||||
|
||||
myBuffer.resize(frameSize);
|
||||
myStartCPUCycles = g_nCumulativeCycles;
|
||||
myPreviousFrameTime = 0;
|
||||
|
||||
const qint32 framePeriod = myAudioFormat.framesForBytes(myAudioOutput->periodSize());
|
||||
qDebug(appleAudio) << "AudioOutput: size =" << frameSize << "f, period =" << framePeriod << "f";
|
||||
|
||||
mySilence = 0;
|
||||
myMaximum = 0;
|
||||
myValue = 0;
|
||||
myPhysical = myVolume;
|
||||
myTicks = std::queue<qint64>();
|
||||
|
||||
writeEnoughSilence(myInitialSilence); // ms
|
||||
}
|
||||
|
||||
void AudioGenerator::writeEnoughSilence(const qint64 ms)
|
||||
{
|
||||
// write a few ms of silence
|
||||
const qint32 framesSilence = myAudioFormat.framesForDuration(ms * 1000); // target frames to write
|
||||
|
||||
const qint32 bytesFree = myAudioOutput->bytesFree();
|
||||
const qint32 framesFree = myAudioFormat.framesForBytes(bytesFree); // number of frames avilable to write
|
||||
|
||||
const qint32 framesToWrite = std::min(framesFree, framesSilence);
|
||||
|
||||
generateSilence(myBuffer.data(), myBuffer.data() + framesToWrite);
|
||||
|
||||
const qint64 bytesToWrite = myAudioFormat.bytesForFrames(framesToWrite);
|
||||
const char * data = reinterpret_cast<char *>(myBuffer.data());
|
||||
myDevice->write(data, bytesToWrite);
|
||||
|
||||
const qint64 duration = myAudioFormat.durationForFrames(framesToWrite);
|
||||
qDebug(appleAudio) << "Written some silence: frames =" << framesToWrite << ", duration =" << duration / 1000 << "ms";
|
||||
}
|
||||
|
||||
void AudioGenerator::stop()
|
||||
{
|
||||
if (!isRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const qint32 bytesFree = myAudioOutput->bytesFree();
|
||||
const qint32 framesFree = myAudioFormat.framesForBytes(bytesFree);
|
||||
|
||||
// fill with zeros and stop
|
||||
generateSilence(myBuffer.data(), myBuffer.data() + framesFree);
|
||||
|
||||
const qint32 bytesToWrite = myAudioFormat.bytesForFrames(framesFree);
|
||||
const char * data = reinterpret_cast<char *>(myBuffer.data());
|
||||
myDevice->write(data, bytesToWrite);
|
||||
|
||||
const qint64 duration = myAudioFormat.durationForFrames(framesFree);
|
||||
qDebug(appleAudio) << "Stopping with silence: frames =" << framesFree << ", duration =" << duration / 1000 << "ms";
|
||||
myAudioOutput->stop();
|
||||
}
|
||||
|
||||
void AudioGenerator::writeAudio()
|
||||
{
|
||||
if (!isRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we write all we have available (up to the free bytes)
|
||||
|
||||
const qint64 currentFrameTime = toFrameTime(g_nCumulativeCycles);
|
||||
const qint64 newFramesAvailable = currentFrameTime - myPreviousFrameTime;
|
||||
|
||||
const qint32 bytesFree = myAudioOutput->bytesFree();
|
||||
const qint64 framesFree = myAudioFormat.framesForBytes(bytesFree);
|
||||
|
||||
const qint64 framesToWrite = std::min(framesFree, newFramesAvailable);
|
||||
|
||||
generateSamples(myBuffer.data(), framesToWrite);
|
||||
|
||||
const qint32 bytesToWrite = myAudioFormat.bytesForFrames(framesToWrite);
|
||||
const char * data = reinterpret_cast<char *>(myBuffer.data());
|
||||
const qint64 bytesWritten = myDevice->write(data, bytesToWrite);
|
||||
if (bytesToWrite != bytesWritten)
|
||||
{
|
||||
qDebug(appleAudio) << "Mismatch:" << bytesToWrite << "!=" << bytesWritten;
|
||||
}
|
||||
const qint64 framesWritten = framesToWrite;
|
||||
myPreviousFrameTime += framesWritten;
|
||||
|
||||
const qint32 bytesFreeNow = myAudioOutput->bytesFree();
|
||||
if (bytesFreeNow > myMaximum)
|
||||
{
|
||||
// if this number is too big, it probably means we aren't providing enough data
|
||||
const qint32 bytesSize = myAudioOutput->bufferSize();
|
||||
myMaximum = bytesFreeNow;
|
||||
qDebug(appleAudio) << "Running maximum free bytes:" << myMaximum << "/" << bytesSize;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGenerator::generateSilence(audio_t * begin, audio_t * end)
|
||||
{
|
||||
if (myValue != 0)
|
||||
{
|
||||
const audio_t delta = myPhysical > 0 ? -1 : +1;
|
||||
for (audio_t * ptr = begin; ptr != end; ++ptr)
|
||||
{
|
||||
++mySilence;
|
||||
if (myValue != 0 && mySilence > mySilenceDelay)
|
||||
{
|
||||
myValue += delta;
|
||||
}
|
||||
*ptr = myValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no need to update mySilence as myValue is already 0
|
||||
std::fill(begin, end, myValue);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGenerator::generateSamples(audio_t *data, qint64 framesToWrite)
|
||||
{
|
||||
qint64 start = myPreviousFrameTime;
|
||||
qint64 end = myPreviousFrameTime + framesToWrite;
|
||||
|
||||
audio_t * head = data;
|
||||
while (!myTicks.empty() && (myTicks.front() < end))
|
||||
{
|
||||
qint64 next = myTicks.front() - start;
|
||||
audio_t * last = data + next;
|
||||
std::fill(head, last, myValue);
|
||||
head = last;
|
||||
myPhysical = -myPhysical;
|
||||
myValue = myPhysical;
|
||||
mySilence = 0;
|
||||
myTicks.pop();
|
||||
}
|
||||
|
||||
generateSilence(head, data + framesToWrite);
|
||||
}
|
59
source/frontends/qapple/audiogenerator.h
Normal file
59
source/frontends/qapple/audiogenerator.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QAudioFormat>
|
||||
#include <QAudioOutput>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class AudioGenerator {
|
||||
public:
|
||||
|
||||
static AudioGenerator & instance();
|
||||
|
||||
QAudioOutput * getAudioOutput();
|
||||
|
||||
void toggle();
|
||||
void start();
|
||||
void stop();
|
||||
void writeAudio();
|
||||
void stateChanged(QAudio::State state);
|
||||
|
||||
void getOptions(qint32 & initialSilence, qint32 & silenceDelay, qint32 & volume) const;
|
||||
void setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume);
|
||||
|
||||
private:
|
||||
typedef short int audio_t;
|
||||
|
||||
std::queue<qint64> myTicks;
|
||||
|
||||
std::shared_ptr<QAudioOutput> myAudioOutput;
|
||||
QAudioFormat myAudioFormat;
|
||||
QIODevice * myDevice;
|
||||
std::vector<audio_t> myBuffer;
|
||||
|
||||
// options
|
||||
qint32 myInitialSilence;
|
||||
qint32 mySilenceDelay;
|
||||
qint16 myVolume;
|
||||
|
||||
qint64 myStartCPUCycles;
|
||||
qint64 myPreviousFrameTime;
|
||||
qint32 myMaximum;
|
||||
|
||||
qint32 mySilence;
|
||||
audio_t myPhysical;
|
||||
audio_t myValue;
|
||||
|
||||
AudioGenerator();
|
||||
|
||||
qint64 toFrameTime(qint64 cpuCycples);
|
||||
void generateSamples(audio_t *data, qint64 framesToWrite);
|
||||
void writeEnoughSilence(const qint64 ms);
|
||||
void generateSilence(audio_t * begin, audio_t * end);
|
||||
bool isRunning();
|
||||
};
|
||||
|
||||
#endif // AUDIO_H
|
|
@ -8,6 +8,7 @@
|
|||
#include "Registry.h"
|
||||
#include "SaveState.h"
|
||||
#include "CPU.h"
|
||||
#include "audiogenerator.h"
|
||||
|
||||
#include "linux/paddle.h"
|
||||
|
||||
|
@ -175,6 +176,8 @@ Preferences::Data getCurrentOptions(const std::shared_ptr<QGamepad> & gamepad)
|
|||
|
||||
currentOptions.screenshotTemplate = getScreenshotTemplate();
|
||||
|
||||
AudioGenerator::instance().getOptions(currentOptions.audioLatency, currentOptions.silenceDelay, currentOptions.volume);
|
||||
|
||||
return currentOptions;
|
||||
}
|
||||
|
||||
|
@ -261,4 +264,6 @@ void setNewOptions(const Preferences::Data & currentOptions, const Preferences::
|
|||
setScreenshotTemplate(newOptions.screenshotTemplate);
|
||||
}
|
||||
|
||||
AudioGenerator::instance().setOptions(newOptions.audioLatency, newOptions.silenceDelay, newOptions.volume);
|
||||
|
||||
}
|
||||
|
|
3
source/frontends/qapple/loggingcategory.cpp
Normal file
3
source/frontends/qapple/loggingcategory.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "loggingcategory.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(appleAudio, "apple.audio")
|
8
source/frontends/qapple/loggingcategory.h
Normal file
8
source/frontends/qapple/loggingcategory.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef LOGGINGCATEGORY_H
|
||||
#define LOGGINGCATEGORY_H
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(appleAudio)
|
||||
|
||||
#endif // LOGGINGCATEGORY_H
|
|
@ -41,7 +41,7 @@
|
|||
<customwidget>
|
||||
<class>QHexEdit</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qhexedit.h</header>
|
||||
<header>qhexedit2/qhexedit.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
|
|
|
@ -188,6 +188,10 @@ void Preferences::setData(const Data & data)
|
|||
|
||||
save_state->setText(data.saveState);
|
||||
screenshot->setText(data.screenshotTemplate);
|
||||
|
||||
audio_latency->setValue(data.audioLatency);
|
||||
silence_delay->setValue(data.silenceDelay);
|
||||
volume->setValue(data.volume);
|
||||
}
|
||||
|
||||
Preferences::Data Preferences::getData() const
|
||||
|
@ -220,6 +224,10 @@ Preferences::Data Preferences::getData() const
|
|||
data.saveState = save_state->text();
|
||||
data.screenshotTemplate = screenshot->text();
|
||||
|
||||
data.audioLatency = audio_latency->value();
|
||||
data.silenceDelay = silence_delay->value();
|
||||
data.volume = volume->value();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,10 @@ public:
|
|||
|
||||
bool enhancedSpeed;
|
||||
|
||||
int audioLatency;
|
||||
int silenceDelay;
|
||||
int volume;
|
||||
|
||||
std::vector<QString> disks;
|
||||
std::vector<QString> hds;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>648</width>
|
||||
<height>315</height>
|
||||
<height>316</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -385,6 +385,70 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="audio">
|
||||
<attribute name="title">
|
||||
<string>Audio</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Audio latency (ms)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="audio_latency">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Silence delay (frames)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="silence_delay">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>50000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Volume</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="volume">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>0x</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>32767</number>
|
||||
</property>
|
||||
<property name="displayIntegerBase">
|
||||
<number>16</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="registry">
|
||||
<attribute name="title">
|
||||
<string>Registry</string>
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "emulator.h"
|
||||
#include "memorycontainer.h"
|
||||
#include "configuration.h"
|
||||
#include "audiogenerator.h"
|
||||
|
||||
#include <QMdiSubWindow>
|
||||
#include <QMessageBox>
|
||||
|
@ -130,18 +131,6 @@ void FrameRefreshStatus(int, bool)
|
|||
|
||||
}
|
||||
|
||||
// Speaker
|
||||
|
||||
BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
|
||||
{
|
||||
Q_UNUSED(pc)
|
||||
Q_UNUSED(addr)
|
||||
Q_UNUSED(bWrite)
|
||||
Q_UNUSED(d)
|
||||
Q_UNUSED(uExecutedCycles)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void VideoInitialize()
|
||||
{
|
||||
VideoReinitialize();
|
||||
|
@ -211,8 +200,11 @@ QApple::QApple(QWidget *parent) :
|
|||
myEmulator = new Emulator(mdiArea);
|
||||
myEmulatorWindow = mdiArea->addSubWindow(myEmulator, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint);
|
||||
|
||||
connect(AudioGenerator::instance().getAudioOutput(), SIGNAL(stateChanged(QAudio::State)), this, SLOT(on_stateChanged(QAudio::State)));
|
||||
|
||||
const int fps = 60;
|
||||
myMSGap = 1000 / fps;
|
||||
myFullSpeedMS = 5;
|
||||
|
||||
on_actionPause_triggered();
|
||||
initialiseEmulator();
|
||||
|
@ -226,8 +218,15 @@ void QApple::closeEvent(QCloseEvent *)
|
|||
uninitialiseEmulator();
|
||||
}
|
||||
|
||||
void QApple::on_stateChanged(QAudio::State state)
|
||||
{
|
||||
AudioGenerator::instance().stateChanged(state);
|
||||
}
|
||||
|
||||
void QApple::on_timer()
|
||||
{
|
||||
AudioGenerator::instance().start();
|
||||
|
||||
if (!myElapsedTimer.isValid())
|
||||
{
|
||||
myElapsedTimer.start();
|
||||
|
@ -237,17 +236,20 @@ void QApple::on_timer()
|
|||
// target x ms ahead of where we are now, which is when the timer should be called again
|
||||
const qint64 target = myElapsedTimer.elapsed() + myMSGap;
|
||||
const qint64 current = emulatorTimeInMS() - myCpuTimeReference;
|
||||
const qint64 elapsed = target - current;
|
||||
if (elapsed <= 0)
|
||||
if (current > target)
|
||||
{
|
||||
// we got ahead of the timer by a lot
|
||||
|
||||
// just check if we got something to write
|
||||
AudioGenerator::instance().writeAudio();
|
||||
|
||||
// wait next call
|
||||
return;
|
||||
}
|
||||
|
||||
const qint64 full_speed_ms = 5;
|
||||
const qint64 toRun = target - current;
|
||||
const double fUsecPerSec = 1.e6;
|
||||
const qint64 nExecutionPeriodUsec = 1000 * elapsed;
|
||||
const qint64 nExecutionPeriodUsec = 1000 * toRun;
|
||||
|
||||
const double fExecutionPeriodClks = g_fCurrentCLK6502 * (double(nExecutionPeriodUsec) / fUsecPerSec);
|
||||
const DWORD uCyclesToExecute = fExecutionPeriodClks;
|
||||
|
@ -265,7 +267,7 @@ void QApple::on_timer()
|
|||
g_dwCyclesThisFrame = g_dwCyclesThisFrame % dwClksPerFrame;
|
||||
++count;
|
||||
}
|
||||
while (sg_Disk2Card.IsConditionForFullSpeed() && (myElapsedTimer.elapsed() < target + full_speed_ms));
|
||||
while (sg_Disk2Card.IsConditionForFullSpeed() && (myElapsedTimer.elapsed() < target + myFullSpeedMS));
|
||||
|
||||
// just repaint each time, to make it simpler
|
||||
// we run @ 60 fps anyway
|
||||
|
@ -275,12 +277,17 @@ void QApple::on_timer()
|
|||
{
|
||||
restartTimeCounters();
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioGenerator::instance().writeAudio();
|
||||
}
|
||||
}
|
||||
|
||||
void QApple::stopTimer()
|
||||
{
|
||||
if (myTimerID)
|
||||
{
|
||||
restartTimeCounters();
|
||||
killTimer(myTimerID);
|
||||
myTimerID = 0;
|
||||
}
|
||||
|
@ -288,6 +295,8 @@ void QApple::stopTimer()
|
|||
|
||||
void QApple::restartTimeCounters()
|
||||
{
|
||||
// let them restart next time
|
||||
AudioGenerator::instance().stop();
|
||||
myElapsedTimer.invalidate();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <QElapsedTimer>
|
||||
#include <QGamepad>
|
||||
#include <QAudio>
|
||||
|
||||
#include <memory>
|
||||
#include "preferences.h"
|
||||
|
||||
|
@ -58,6 +60,8 @@ private slots:
|
|||
|
||||
void on_actionSwap_disks_triggered();
|
||||
|
||||
void on_stateChanged(QAudio::State state);
|
||||
|
||||
private:
|
||||
|
||||
// helper class to pause the emulator and restart at the end of the block
|
||||
|
@ -86,6 +90,7 @@ private:
|
|||
qint64 myCpuTimeReference;
|
||||
|
||||
int myMSGap;
|
||||
qint64 myFullSpeedMS;
|
||||
};
|
||||
|
||||
#endif // QAPPLE_H
|
||||
|
|
|
@ -4,41 +4,43 @@
|
|||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets gamepad
|
||||
QT += core gui multimedia widgets gamepad
|
||||
|
||||
TARGET = qapple
|
||||
TEMPLATE = app
|
||||
|
||||
|
||||
SOURCES += main.cpp\
|
||||
qapple.cpp \
|
||||
audiogenerator.cpp \
|
||||
loggingcategory.cpp \
|
||||
qapple.cpp \
|
||||
qresources.cpp \
|
||||
emulator.cpp \
|
||||
video.cpp \
|
||||
graphicscache.cpp \
|
||||
chunks.cpp \
|
||||
commands.cpp \
|
||||
qhexedit.cpp \
|
||||
memorycontainer.cpp \
|
||||
preferences.cpp \
|
||||
gamepadpaddle.cpp \
|
||||
settings.cpp \
|
||||
configuration.cpp
|
||||
configuration.cpp \
|
||||
qhexedit2/chunks.cpp \
|
||||
qhexedit2/commands.cpp \
|
||||
qhexedit2/qhexedit.cpp
|
||||
|
||||
HEADERS += qapple.h \
|
||||
audiogenerator.h \
|
||||
emulator.h \
|
||||
loggingcategory.h \
|
||||
video.h \
|
||||
graphicscache.h \
|
||||
chunks.h \
|
||||
commands.h \
|
||||
qhexedit.h \
|
||||
memorycontainer.h \
|
||||
preferences.h \
|
||||
gamepadpaddle.h \
|
||||
settings.h \
|
||||
configuration.h
|
||||
configuration.h \
|
||||
qhexedit2/chunks.h \
|
||||
qhexedit2/commands.h \
|
||||
qhexedit2/qhexedit.h
|
||||
|
||||
FORMS += qapple.ui \
|
||||
emulator.ui \
|
||||
|
|
Loading…
Add table
Reference in a new issue