AppleWin/source/frontends/qapple/audiogenerator.cpp
Andrea Odetti 24e0b17135 Works on Raspbian.
Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
2019-12-14 21:18:00 +00:00

258 lines
7.3 KiB
C++

#include "StdAfx.h"
#include "audiogenerator.h"
#include "Common.h"
#include "CPU.h"
#include "Applewin.h"
#include "Memory.h"
#include "loggingcategory.h"
#include <QDebug>
#include <QAudioOutput>
// 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::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;
}
// restart as we are either starting or recovering from underrun
myDevice = myAudioOutput->start();
if (!myDevice)
{
return;
}
qDebug(appleAudio) << "Restarting the AudioGenerator";
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);
}