Make Mockingboard voice actually play.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2020-07-02 11:02:57 +01:00
parent 04ef0bf377
commit 5dee29fbf9
12 changed files with 361 additions and 19 deletions

View file

@ -547,3 +547,12 @@ BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecu
return MemReadFloatingBus(uExecutedCycles);
}
// Mockingboard
void registerSoundBuffer(IDirectSoundBuffer * buffer)
{
}
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
{
}

View file

@ -21,6 +21,7 @@ add_executable(qapple
audiogenerator.cpp
loggingcategory.cpp
viewbuffer.cpp
qdirectsound.cpp
${QHEXVIEW_SOURCES}
)

View file

@ -29,6 +29,7 @@
#include "emulator.h"
#include "memorycontainer.h"
#include "audiogenerator.h"
#include "qdirectsound.h"
#include "gamepadpaddle.h"
#include "preferences.h"
@ -288,6 +289,7 @@ void QApple::on_stateChanged(QAudio::State state)
void QApple::on_timer()
{
AudioGenerator::instance().start();
QDirectSound::start();
if (!myElapsedTimer.isValid())
{
@ -304,6 +306,7 @@ void QApple::on_timer()
// just check if we got something to write
AudioGenerator::instance().writeAudio();
QDirectSound::writeAudio();
// wait next call
return;
@ -345,6 +348,7 @@ void QApple::on_timer()
else
{
AudioGenerator::instance().writeAudio();
QDirectSound::writeAudio();
}
}
@ -362,6 +366,7 @@ void QApple::restartTimeCounters()
{
// let them restart next time
AudioGenerator::instance().stop();
QDirectSound::stop();
myElapsedTimer.invalidate();
}
@ -465,6 +470,7 @@ void QApple::reloadOptions()
Paddle::instance() = GamepadPaddle::fromName(myOptions.gamepadName);
AudioGenerator::instance().setOptions(myOptions.audioLatency, myOptions.silenceDelay, myOptions.volume);
QDirectSound::setOptions(myOptions.audioLatency, myOptions.silenceDelay, myOptions.volume);
}
void QApple::on_actionSave_state_triggered()

View file

@ -27,6 +27,7 @@ SOURCES += main.cpp\
loggingcategory.cpp \
options.cpp \
qapple.cpp \
qdirectsound.cpp \
qresources.cpp \
emulator.cpp \
registry.cpp \
@ -53,6 +54,7 @@ HEADERS += qapple.h \
emulator.h \
loggingcategory.h \
options.h \
qdirectsound.h \
registry.h \
video.h \
memorycontainer.h \

View file

@ -0,0 +1,251 @@
#include "qdirectsound.h"
#include "loggingcategory.h"
#include "qdirectsound.h"
#include "linux/windows/dsound.h"
#include <unordered_map>
#include <memory>
#include <QAudioOutput>
namespace
{
class DirectSoundGenerator
{
public:
DirectSoundGenerator(IDirectSoundBuffer * buffer);
void start();
void stop();
void writeAudio();
void stateChanged(QAudio::State state);
void setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume);
private:
IDirectSoundBuffer * myBuffer;
typedef short int audio_t;
std::shared_ptr<QAudioOutput> myAudioOutput;
QAudioFormat myAudioFormat;
QIODevice * myDevice;
// options
qint32 myInitialSilence;
qint32 mySilenceDelay;
bool isRunning();
void initialise();
void writeEnoughSilence(const qint64 ms);
};
std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators;
DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer) : myBuffer(buffer)
{
myInitialSilence = 200;
mySilenceDelay = 10000;
}
void DirectSoundGenerator::initialise()
{
// only initialise here to skip all the buffers which are not in DSBSTATUS_PLAYING mode
QAudioFormat audioFormat;
audioFormat.setSampleRate(myBuffer->sampleRate);
audioFormat.setChannelCount(myBuffer->channels);
audioFormat.setSampleSize(myBuffer->bitsPerSample);
audioFormat.setCodec(QString::fromUtf8("audio/pcm"));
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
audioFormat.setSampleType(QAudioFormat::SignedInt);
myAudioOutput.reset(new QAudioOutput(audioFormat));
myAudioFormat = myAudioOutput->format();
}
void DirectSoundGenerator::setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume)
{
myInitialSilence = std::max(0, initialSilence);
mySilenceDelay = std::max(0, silenceDelay);
}
bool DirectSoundGenerator::isRunning()
{
if (!myAudioOutput)
{
return false;
}
const QAudio::State state = myAudioOutput->state();
const QAudio::Error error = myAudioOutput->error();
if (state == QAudio::ActiveState)
{
return true;
}
if (state == QAudio::IdleState && error == QAudio::NoError)
{
return true;
}
return false;
}
void DirectSoundGenerator::start()
{
if (isRunning())
{
return;
}
DWORD dwStatus;
myBuffer->GetStatus(&dwStatus);
if (!(dwStatus & DSBSTATUS_PLAYING))
{
return;
}
if (!myAudioOutput)
{
initialise();
}
// 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);
const qint32 framePeriod = myAudioFormat.framesForBytes(myAudioOutput->periodSize());
qDebug(appleAudio) << "AudioOutput: size =" << frameSize << "f, period =" << framePeriod << "f";
writeEnoughSilence(myInitialSilence); // ms
}
void DirectSoundGenerator::stop()
{
if (!isRunning())
{
return;
}
const qint32 bytesFree = myAudioOutput->bytesFree();
// fill with zeros and stop
std::vector<char> silence(bytesFree);
myDevice->write(silence.data(), silence.size());
const qint32 framesFree = myAudioFormat.framesForBytes(bytesFree);
const qint64 duration = myAudioFormat.durationForFrames(framesFree);
qDebug(appleAudio) << "Stopping with silence: frames =" << framesFree << ", duration =" << duration / 1000 << "ms";
myAudioOutput->stop();
}
void DirectSoundGenerator::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);
const qint64 bytesToWrite = myAudioFormat.bytesForFrames(framesToWrite);
std::vector<char> silence(bytesToWrite);
myDevice->write(silence.data(), silence.size());
const qint64 duration = myAudioFormat.durationForFrames(framesToWrite);
qDebug(appleAudio) << "Written some silence: frames =" << framesToWrite << ", duration =" << duration / 1000 << "ms";
}
void DirectSoundGenerator::writeAudio()
{
if (!isRunning())
{
return;
}
// we write all we have available (up to the free bytes)
const DWORD bytesFree = myAudioOutput->bytesFree();
LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
// this function reads as much as possible up to bytesFree
myBuffer->Read(bytesFree, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
qint64 bytesWritten = 0;
qint64 bytesToWrite = 0;
if (lpvAudioPtr1)
{
bytesWritten += myDevice->write((char *)lpvAudioPtr1, dwAudioBytes1);
bytesToWrite += dwAudioBytes1;
}
if (lpvAudioPtr2)
{
bytesWritten += myDevice->write((char *)lpvAudioPtr2, dwAudioBytes2);
bytesToWrite += dwAudioBytes2;
}
if (bytesToWrite != bytesWritten)
{
qDebug(appleAudio) << "Mismatch:" << bytesToWrite << "!=" << bytesWritten;
}
}
}
void registerSoundBuffer(IDirectSoundBuffer * buffer)
{
const std::shared_ptr<DirectSoundGenerator> generator = std::make_shared<DirectSoundGenerator>(buffer);
activeSoundGenerators[buffer] = generator;
}
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
{
activeSoundGenerators.erase(buffer);
}
namespace QDirectSound
{
void start()
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->start();
}
}
void stop()
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->stop();
}
}
void writeAudio()
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->writeAudio();
}
}
void setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume)
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->setOptions(initialSilence, silenceDelay, volume);
}
}
}

View file

@ -0,0 +1,15 @@
#ifndef DIRECTSOUND_H
#define DIRECTSOUND_H
#include <QtGlobal>
namespace QDirectSound
{
void start();
void stop();
void writeAudio();
void setOptions(const qint32 initialSilence, const qint32 silenceDelay, const qint32 volume);
}
#endif // DIRECTSOUND_H

View file

@ -1,6 +1,7 @@
#pragma once
#include "linux/windows/wincompat.h"
#include "linux/windows/dsound.h"
#include "linux/windows/resources.h"
#include "linux/windows/bitmap.h"
@ -23,19 +24,19 @@ void FrameRefreshStatus(int x, bool);
// Keyboard
BYTE KeybGetKeycode ();
BYTE KeybGetKeycode ();
BYTE KeybReadData();
BYTE KeybReadFlag();
// Joystick
BYTE __stdcall JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
BYTE __stdcall JoyReadPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
BYTE JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
BYTE JoyReadPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
void JoyResetPosition(ULONG uExecutedCycles);
// Speaker
BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
BYTE SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
// Registry
@ -48,3 +49,7 @@ void RegSaveValue (LPCTSTR section, LPCTSTR key, BOOL peruser, DWORD value);
// MessageBox
int MessageBox(HWND, const char * text, const char * caption, UINT type);
// Mockingboard
void registerSoundBuffer(IDirectSoundBuffer * buffer);
void unregisterSoundBuffer(IDirectSoundBuffer * buffer);

View file

@ -1,5 +1,6 @@
#include "linux/windows/dsound.h"
#include "linux/windows/winerror.h"
#include "linux/interface.h"
#include <cstring>
@ -8,15 +9,21 @@ HRESULT IDirectSoundNotify::SetNotificationPositions(DWORD cPositionNotifies, LP
return DS_OK;
}
IDirectSoundBuffer::IDirectSoundBuffer(const size_t bufferSize, const size_t channels, const size_t sampleRate, const size_t bitsPerSample, const size_t flags)
: myBufferSize(bufferSize)
, myChannels(channels)
, mySampleRate(sampleRate)
, myBitsPerSample(bitsPerSample)
, myFlags(flags)
IDirectSoundBuffer::IDirectSoundBuffer(const size_t aBufferSize, const size_t aChannels, const size_t aSampleRate, const size_t aBitsPerSample, const size_t aFlags)
: bufferSize(aBufferSize)
, channels(aChannels)
, sampleRate(aSampleRate)
, bitsPerSample(aBitsPerSample)
, flags(aFlags)
, mySoundNotify(new IDirectSoundNotify)
, mySoundBuffer(bufferSize)
, mySoundBuffer(aBufferSize)
{
registerSoundBuffer(this);
}
IDirectSoundBuffer::~IDirectSoundBuffer()
{
unregisterSoundBuffer(this);
}
HRESULT IDirectSoundBuffer::QueryInterface(int riid, void **ppvObject)
@ -113,9 +120,37 @@ HRESULT IDirectSoundBuffer::Lock( DWORD dwWriteCursor, DWORD dwWriteBytes, LPVOI
return DS_OK;
}
HRESULT IDirectSoundBuffer::Read( DWORD dwReadBytes, LPVOID * lplpvAudioPtr1, DWORD * lpdwAudioBytes1, LPVOID * lplpvAudioPtr2, DWORD * lpdwAudioBytes2)
{
const DWORD available = (this->myWritePosition - this->myPlayPosition) % this->bufferSize;
dwReadBytes = std::min(dwReadBytes, available);
const DWORD availableInFirstPart = this->mySoundBuffer.size() - this->myPlayPosition;
*lplpvAudioPtr1 = this->mySoundBuffer.data() + this->myPlayPosition;
*lpdwAudioBytes1 = std::min(availableInFirstPart, dwReadBytes);
if (lplpvAudioPtr2 && lpdwAudioBytes2)
{
if (*lpdwAudioBytes1 < dwReadBytes)
{
*lplpvAudioPtr2 = this->mySoundBuffer.data();
*lpdwAudioBytes2 = dwReadBytes - *lpdwAudioBytes1;
}
else
{
*lplpvAudioPtr2 = nullptr;
*lpdwAudioBytes2 = 0;
}
}
this->myPlayPosition = (this->myPlayPosition + dwReadBytes) % this->mySoundBuffer.size();
return DS_OK;
}
HRESULT IDirectSoundBuffer::GetCurrentPosition( LPDWORD lpdwCurrentPlayCursor, LPDWORD lpdwCurrentWriteCursor )
{
*lpdwCurrentPlayCursor = this->myWritePosition;
*lpdwCurrentPlayCursor = this->myPlayPosition;
*lpdwCurrentWriteCursor = this->myWritePosition;
return DS_OK;
}

View file

@ -75,14 +75,8 @@ typedef struct IDirectSoundNotify *LPDIRECTSOUNDNOTIFY,**LPLPDIRECTSOUNDNOTIFY;
class IDirectSoundBuffer : public IUnknown
{
const size_t myBufferSize;
const size_t mySampleRate;
const size_t myChannels;
const size_t myBitsPerSample;
const size_t myFlags;
std::unique_ptr<IDirectSoundNotify> mySoundNotify;
std::vector<SHORT> mySoundBuffer;
std::vector<char> mySoundBuffer;
size_t myPlayPosition = 0;
size_t myWritePosition = 0;
@ -90,13 +84,23 @@ class IDirectSoundBuffer : public IUnknown
LONG myVolume = DSBVOLUME_MIN;
public:
const size_t bufferSize;
const size_t sampleRate;
const size_t channels;
const size_t bitsPerSample;
const size_t flags;
IDirectSoundBuffer(const size_t bufferSize, const size_t channels, const size_t sampleRate, const size_t bitsPerSample, const size_t flags);
virtual ~IDirectSoundBuffer();
HRESULT QueryInterface(int riid, void **ppvObject);
HRESULT SetCurrentPosition( DWORD dwNewPosition );
HRESULT GetCurrentPosition( LPDWORD lpdwCurrentPlayCursor, LPDWORD lpdwCurrentWriteCursor );
// Read is NOT part of Windows API
HRESULT Read( DWORD dwReadBytes, LPVOID * lplpvAudioPtr1, DWORD * lpdwAudioBytes1, LPVOID * lplpvAudioPtr2, DWORD * lpdwAudioBytes2);
HRESULT Lock( DWORD dwWriteCursor, DWORD dwWriteBytes, LPVOID * lplpvAudioPtr1, DWORD * lpdwAudioBytes1, LPVOID * lplpvAudioPtr2, DWORD * lpdwAudioBytes2, DWORD dwFlags );
HRESULT Unlock( LPVOID lpvAudioPtr1, DWORD dwAudioBytes1, LPVOID lpvAudioPtr2, DWORD dwAudioBytes2 );

View file

@ -1,6 +1,10 @@
#include "linux/windows/guiddef.h"
#include "linux/windows/winerror.h"
IUnknown::~IUnknown()
{
}
HRESULT IUnknown::QueryInterface(int riid, void **ppvObject)
{
return S_OK;

View file

@ -15,6 +15,8 @@ struct IUnknown
{
HRESULT QueryInterface(int riid, void **ppvObject);
HRESULT Release();
virtual ~IUnknown();
};
typedef IUnknown *LPUNKNOWN;

View file

@ -1,5 +1,13 @@
#include "linux/interface.h"
void registerSoundBuffer(IDirectSoundBuffer * buffer)
{
}
void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
{
}
// Resources
HRSRC FindResource(void *, const std::string & filename, const char *)